Доступ к общей переменной в потоковой передаче

#c# #.net #multithreading #thread-safety #threadpool

Вопрос:

Я использую приведенный ниже код и использую Task.Run внутри цикла foeach, чтобы каждый цикл мог выполняться в parellel , потому что у меня есть некоторая логика внутри цикла foreach, которая занимает некоторое время, но проблема, с которой я сталкиваюсь, заключается в том, что TContext.SecondNodeId = otherNode; иногда печатается как 10 или 11, я хочу напечатать это последовательно, как первые 10, а затем 11, потому что у меня есть некоторый код, для которого эти значения имеют значение для логики.

 class Program
{
    static void Main(string[] args)
    {
        TempData tContext = new TempData();
        var nodes = new List<int>() { 1, 2, 3, 4, 5, 6 };
        var otherNodes = new List<int> { 10, 11 };
        foreach (var node in nodes)
        {
            tContext.firstNodeId = node;
            List<Task> tasks = new List<Task>();
            foreach (var otherNode in otherNodes)
            {
                Task t = Task.Run(() =>
                {
                   
                     tContext.SecondNodeId = otherNode;
                    Console.WriteLine("First Node id is"   tContext.firstNodeId   "Second Node Id is "   tContext.SecondNodeId);
                    // I have some long running code which uses first node and second node id.
                });
                tasks.Add(t);
            }
            Task.WaitAll(tasks.ToArray());
        }
    }
}
 

Пожалуйста, предложите способ достижения этой цели.

Ниже приведен вывод.

введите описание изображения здесь

Ответ №1:

Внутри внутреннего цикла, который вы вызываете
Task t = Task.Run(() =>...);

Который запускает лямбду в другом потоке. Внутри этой лямбды вы устанавливаете:

tContext.SecondNodeId = otherNode;

а затем считывание с этого значения. Поскольку у вас есть несколько потоков, записывающих это значение, оно может варьироваться между значениями 10 и 11 в зависимости от того, сколько времени требуется для их выполнения. Другими словами, внутренний цикл может выполнить несколько итераций до того, как ваша порожденная задача будет выполнена. Это то, что известно как «состояние расы».

Итак, предположим, что выполняется первая итерация внутреннего цикла, и задача доходит до точки настройки tContext.SecondNodeId =10 . После этой строки вполне возможно, что вторая итерация цикла будет только что завершена, и порожденная задача установит значение 11. Затем, когда остальная часть первой задачи будет выполнена, она будет использовать значение 11 вместо 10.

Обходным путем было бы использование локальной переменной:

var secondNodeId = otherNode;

И используйте это в своей лямбде. Это недоступно для других задач и не изменится.

Комментарии:

1. Спасибо за разъяснение, если я буду использовать локальную переменную, я не могу использовать свое свойство объекта, которое я хотел бы задать для tcontext.secondnodeId.. Можем ли мы добиться этого с помощью блокировки или любым другим способом, чтобы я мог использовать свой объект класса.

2. Вы можете вернуть значение из своей задачи, а затем использовать его для установки значения. Альтернативой является создание объекта блокировки и блокировка вокруг точки, в которой вы изменяете переменную. Обратите внимание, что это должен быть один объект блокировки, используемый всеми задачами, которые его изменяют, поэтому, вероятно, он может быть создан в начале метода.

3. Я попытался использовать блокировку, но все равно вижу, что два идентификатора одинаковы .. частный статический объект только для чтения ob = новый объект(); блокировка (ob) { TContext.SecondNodeId = Другой код; }

4. Вам придется опубликовать полный код, чтобы показать, как вы обращаетесь к переменной, чтобы я мог объяснить, почему она не работает.

5. codeshare.io/5PZDXP общий код по этой ссылке