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:
This is, when worker is doing its job:
This is, when my code handles progress reported by the worker via ReportProgress
:
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 Task
s 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.
CodePudding user response:
I found the problem after switching to Task
s.
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.