IProgress<T> и Parallel.ForEach Проблемы с синхронизацией

Я сталкиваюсь с проблемой синхронизации, связанной с сообщением о прогрессе внутри Parallel.ForEach. Я воссоздал упрощенную версию проблемы в консольном приложении. Пример на самом деле использует только один элемент в списке. Вот код:

 class Program
{
    static void Main(string[] args)
    {
        int tracker = 0;

        Parallel.ForEach(Enumerable.Range(1, 1), (item) =>
        {
                var progress = new Progress<int>((p) =>
                {
                    tracker = p;
                    Console.WriteLine(String.Format("{0}", p));
                });

                Test(progress);
         });


        Console.WriteLine("The last value is: {0}", tracker);
        Console.ReadKey();
    }

    static void Test(IProgress<int> progress)
    {
        for (int i = 0; i < 20; i++)
        {
            progress.Report(i);
        }
    }
}

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

Как вы можете видеть, строка, которую я ожидаю увидеть последней, не выводится последней и не содержит 20. Но если я удаляю отчеты о ходе выполнения и просто записываю данные в цикл for следующим образом:

class Program
{
    static void Main(string[] args)
    {
        int tracker = 0;

        Parallel.ForEach(Enumerable.Range(1, 1), (item) =>
        {
            tracker = Test();
        });


        Console.WriteLine("The last value is: {0}", tracker);
        Console.ReadKey();
    }

    static int Test()
    {
        int i;
        for ( i = 0; i < 20; i++)
        {
            Console.WriteLine(i.ToString());
        }
        return i;
    }
}

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

ведет себя как я ожидаю. Насколько я знаю, Parallel.ForEach создает задачу для каждого элемента в списке, а IProgress фиксирует контекст, в котором он создан. Учитывая, что это консольное приложение, я не думаю, что это будет иметь значение. Помогите, пожалуйста!

1 ответ

Решение

Объяснение в значительной степени именно то, что написано в документации:

Любой обработчик, предоставленный конструктору или обработчикам событий, зарегистрированным в событии ProgressChanged, вызывается через экземпляр SynchronizationContext, захваченный при его создании. Если во время создания нет текущего SynchronizationContext, обратные вызовы будут вызываться в ThreadPool.

Используя Progress<T>.Report вы фактически ставите в очередь 20 задач в пуле потоков. Там нет никакой гарантии относительно того, в каком порядке они выполнены.

Другие вопросы по тегам