Home > Software design >  Why BackgroundWorker's Progress event runs on a different thread than it should?
Why BackgroundWorker's Progress event runs on a different thread than it should?

Time:10-07

I'm having problems with BackgroundWorker. It seems like it calls its Progress event in a different thread than it was created on, contrary to the documentation. First, for reference:

This is, when worker is being created:

BackgroundWorker created

This is, when worker is doing its job:

Asynchronous work

This is, when my code handles progress reported by the worker via ReportProgress:

Handling progress

Note, that the last thread is even different from the previous two (main and BW's worker thread)

The worker is created in App.xaml.cs indirectly:

public App()
{
    Configuration.Configure(Container.Instance);

    // (...)

    // Configure core service
    coreService = Container.Instance.Resolve<ICoreService>();
    coreService.Init();
}

And then:

public CoreService(ITfsService tfsService, IConfigurationService configurationService, IMapper mapper, IDialogService dialogService)
{
    this.tfsService = tfsService;
    this.configurationService = configurationService;
    this.dialogService = dialogService;
    worker = new CoreWorker(tfsService, configurationService);
    worker.ProgressChanged  = HandleCoreWorkerProgress;
}

Worker gets executed in CoreService.Init():

public void Init()
{
    tfsService.Connect();
    worker.RunWorkerAsync();
}

Worker itself looks more less like this:

private class CoreWorker : BackgroundWorker
{
    private const int CORE_WORKER_TIMEOUT = 1000;
    private readonly ITfsService tfsService;
    private readonly IConfigurationService configurationService;

    // (...)

    public CoreWorker(ITfsService tfsService, IConfigurationService configurationService)
    {
        this.tfsService = tfsService;
        this.configurationService = configurationService;

        this.WorkerSupportsCancellation = true;
        this.WorkerReportsProgress = true;
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        while (true)
        {
            if (this.CancellationPending)
                break;

            if (EnsureTfsServiceUp())
            {
                (var startedBuilds, var succeededBuilds, var failedBuilds) = ProcessBuilds();

                // Download and process builds
                var buildResult = new BuildInfo(startedBuilds, succeededBuilds, failedBuilds);
                ReportProgress(0, buildResult);
            }

            // Wait 1 minute

            Thread.Sleep(CORE_WORKER_TIMEOUT);
        }
    }
}

Note, that interally I use async methods, but I wait for them explicitly (since I'm in a separate thread anyway):

(reachedSince, recentBuilds) = tfsService.GetBuilds(lastBuild.Value).Result;
runningBuilds = tfsService.GetBuilds(runningBuildIds).Result;
plannedBuilds = tfsService.GetBuilds(plannedBuildIds).Result;

Is there something I'm missing? Docs says, that progress handling should happen in the same thread as when BackgroundWorker was created. But it is not, and I'm obviously getting errors on the UI.

Application is WPF, however it works in background, doesn't have main window, most of its job is done in the CoreService and it displays windows only occasionally.

How can I fix that, so that ReportProgress will run in the main thread?


Update: I modified my code to use Tasks instead and used Progress<T> to process the progress:

            notificationTask = Task.Factory.StartNew(async () => await ProcessNotifications(new Progress<BaseCoreNotification>(HandleNotificationProgress), notificationTaskCancellationSource), 
                notificationTaskCancellationSource.Token, 
                TaskCreationOptions.LongRunning, 
                TaskScheduler.Default).Unwrap();

Result is that the progress is still not processed in main thread.

Still not on main thread

CodePudding user response:

I found the problem after switching to Tasks.

In that case, to report progress, I needed to use Progress<T>. This class is supposed to call the delegate in the thread it was constructed in, but in turn it just took a thread from the pool.

The reason was that initialization of my service (where Progress<T> was created) has been done in App ctor and it turs out, that there is no SynchronizationContext there (that is, SynchronizationContext.Current is null). In such case Progress<T> simply uses thread from pool and - most likely - BackgroundWorker does it too.

The Solution was to move initializaiton of my service to Application_Startup, where SynchronizationContext for main thread is already available.

  • Related