BackgroundWorker ReportProgress состояние гонки - PullRequest
1 голос
/ 30 апреля 2020

Приведенный ниже код является наиболее базовым c примером BackgroundWorker.

Таким образом, в основном есть два потока: один основной и один рабочий поток. Всякий раз, когда рабочий поток вызывает ReportProgress, bw_ProgressChanged вызывается в основном потоке (так работает BackgroundWorker).

Если рабочий поток вызывает ReportProgress без каких-либо задержек, основной поток не обрабатывает вызовы bw_ProgressChanged по порядку. Может кто-нибудь объяснить более подробно, почему это происходит?

using System;
using System.ComponentModel;
using System.Threading;

namespace _0042_BackgroundWorkerReportProgress
{
    class Program
    {
        static BackgroundWorker _bw;

        static void Main()
        {
            _bw = new BackgroundWorker
            {
                WorkerReportsProgress = true,
                WorkerSupportsCancellation = true
            };
            _bw.DoWork += bw_DoWork;
            _bw.ProgressChanged += bw_ProgressChanged;
            _bw.RunWorkerCompleted += bw_RunWorkerCompleted;

            _bw.RunWorkerAsync("Hello to worker");

            Console.WriteLine("Press Enter in the next 5 seconds to cancel");
            Console.ReadLine();
            if (_bw.IsBusy) _bw.CancelAsync();
            Console.ReadLine();
        }

        static void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i <= 100; i += 20)
            {
                if (_bw.CancellationPending) { e.Cancel = true; return; }
                _bw.ReportProgress(i);
                Thread.Sleep(1000);  // Without this, there is a race condition.
            }                            
            e.Result = 123;
        }

        static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
                Console.WriteLine("You canceled!");
            else if (e.Error != null)
                Console.WriteLine("Worker exception: " + e.Error.ToString());
            else
                Console.WriteLine("Complete: " + e.Result);
        }

        static void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Console.WriteLine("Reached " + e.ProgressPercentage + "%");
        }
    }
}

1 Ответ

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

BackgroundWorker фиксирует текущий SynchronizationContext в момент вызова RunWorkerAsync ( исходный код ). Когда вызывается метод ReportProgress ( исходный код ), он вызывает событие ProgressChanged, публикуя ранее захваченный SynchronizationContext. Если не было SynchronizationContext.Current для захвата (что происходит по умолчанию в консольном приложении), экземпляр базового SynchronizationContext класса создается и сохраняется как захваченный контекст:

Класс SynchronizationContext - это базовый класс, предоставляющий контекст с бесплатными потоками без синхронизации.

Это означает, что событие ProgressChanged вызывается в случайном потоке ThreadPool.

Если вы хотите использовать класс BackgroundWorker в консольном приложении и вести себя аналогично с приложением WinForms / WPF, вы должны установить реализацию SynchronizationContext, которая не является свободнопоточной. Вы можете найти подходящую реализацию здесь .

...