Метод обмена между потоками - PullRequest
0 голосов
/ 05 апреля 2020

У меня есть Func, например:

int loopMax = 10, taskMax = 10;
int executionCounter = 0;

Func<int> calculator = new Func<int>(() =>
{
    executionCounter++;
    int result = 0;
    for (int i = 0; i < loopMax; i++)
    {
        Thread.Sleep(100);
        if (result + i >= int.MaxValue)
            result = 0;
        result += i;
    }
    return result;
});

, который может вызываться несколькими потоками. Например, вот так:

Task[] tasks = new Task[taskMax];
for (int i = 0; i < taskMax; i++)
{
    tasks[i] = Task.Run(() => _=calculator());
}
Task.WaitAll(tasks);

Мне нужно разделить функцию calculator во всех потоках, и эта функция вызывается только один раз. На самом деле значение переменной executionCounter после выполнения этого кода должно оставаться равным 1, и все потоки должны иметь одинаковое возвращаемое значение.

UPDATE 1

Я думаю, Я могу решить эту проблему, если найду способ серверного первого потока и заблокирую все остальные потоки и после завершения вызова метода первого потока сообщу результат методов другим потокам, а также отменим их, чтобы они не вызывали calculator снова.

Использование lock внутри метода также не то, что я ищу, потому что в этом случае снова вызывается калькулятор несколько раз ...

Ответы [ 2 ]

2 голосов
/ 05 апреля 2020

Похоже, вам нужен класс Lazy<T>. Этот класс обеспечивает поддержку отложенной инициализации. Вот как вы можете его использовать:

Lazy<int> lazyCalculator = new Lazy<int>(calculator);

Task[] tasks = new Task[taskMax];
for (int i = 0; i < taskMax; i++)
{
    tasks[i] = Task.Run(() => _ = lazyCalculator.Value);
}
Task.WaitAll(tasks);

Когда создается экземпляр Lazy, он может принимать необязательный аргумент LazyThreadSafetyMode. Значением этого аргумента по умолчанию является ExecutionAndPublication, с поведением, описанным ниже:

Блокировки используются, чтобы гарантировать, что только один поток может инициализировать экземпляр Lazy<T> потокобезопасным способом.

0 голосов
/ 05 апреля 2020

Похоже, вы хотите, чтобы ваш Calculator метод мог выполняться любым потоком, но этот метод должен выполняться только один раз . Если это правда, то мы будем использовать оператор lock.

Назначение оператора блокировки: :

Оператор блокировки получает взаимное исключение блокировка для данного объекта, выполняет блок операторов, а затем снимает блокировку

Пример:

static object lockCalculatorMethod = new object();
static int executionCounter = 0;
static int loopMax = 10;
static int taskMax = 10;


static void Main(string[] args)
{
    Task[] tasks = new Task[taskMax];
    for (int i = 0; i < taskMax; i++)
    {
        tasks[i] = Task.Run(() => _ = Calculator());
    }
    Task.WhenAll(tasks);
}

и метод калькулятора:

static int Calculator()
{
    lock (lockCalculatorMethod)
    {
        if (executionCounter < 1)
        {
            executionCounter++;
            int result = 0;
            for (int i = 0; i < loopMax; i++)
            {
                Thread.Sleep(100);
                if (result + i >= int.MaxValue)
                {
                    result = 0;
                    result += i;
                }
            }
            return result;
         }
         else
             return -1;
     }
}

ОБНОВЛЕНИЕ:

Если вы хотите кэшировать результат и избежать пересчета при вызове другими потоками, тогда вы можете использовать threadSafe collection ConcurrentQueue<T> и просто получать элементы из этого коллекция:

static object lockCalculatorMethod = new object();
static ConcurrentQueue<int> queue = new ConcurrentQueue<int>();

static int executionCounter = 0;
static int loopMax = 7;
static int taskMax = 7;


static void Main(string[] args)
{
    Task[] tasks = new Task[taskMax];
    for (int i = 0; i < taskMax; i++)
    {
        tasks[i] = Task.Run(() => 
        {
            var result = Calculator();
            Console.WriteLine(result);
        });
    }
    Task.WaitAll(tasks);
}

и Calculator метод:

static int Calculator()
{
    int result = 0;
    lock (lockCalculatorMethod)
    {
        int lockResult = 0;
        if (executionCounter < 1)
        {
            executionCounter++;
            for (int i = 0; i < loopMax; i++)
            {
                Thread.Sleep(100);                        
                lockResult += i;
            }
            queue.Enqueue(lockResult);
        }
    }
    queue.TryPeek(out result);
    return result;
} 
...