为什么 BackgroundWorker 的 Progress 事件运行在不同的线程上?
Why BackgroundWorker's Progress event runs on a different thread than it should?
我在使用 BackgroundWorker
时遇到问题。它似乎在与创建时不同的线程中调用其 Progress 事件,这与文档相反。一、供参考:
这是在创建 worker 时:
这是,当工人正在做它的工作时:
这是,当我的代码处理工作人员通过 ReportProgress
:
报告的进度时
请注意,最后一个线程甚至与前两个(主线程和 BW 的工作线程)不同
工人是在App.xaml.cs中间接创建的:
public App()
{
Configuration.Configure(Container.Instance);
// (...)
// Configure core service
coreService = Container.Instance.Resolve<ICoreService>();
coreService.Init();
}
然后:
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 在 CoreService.Init()
中执行:
public void Init()
{
tfsService.Connect();
worker.RunWorkerAsync();
}
Worker 本身看起来更像这样:
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);
}
}
}
请注意,我在内部使用异步方法,但我明确地等待它们(因为无论如何我都在一个单独的线程中):
(reachedSince, recentBuilds) = tfsService.GetBuilds(lastBuild.Value).Result;
runningBuilds = tfsService.GetBuilds(runningBuildIds).Result;
plannedBuilds = tfsService.GetBuilds(plannedBuildIds).Result;
有什么我想念的吗?文档说,进度处理应该发生在与创建 BackgroundWorker
时相同的线程中。但事实并非如此,而且我显然在 UI.
上遇到了错误
应用程序是 WPF,但是它在后台运行,没有主程序 window,它的大部分工作是在 CoreService 中完成的,它只是偶尔显示 windows。
我该如何解决这个问题,以便 ReportProgress 运行 进入主线程?
Update:我修改了我的代码以使用 Task
s 代替并使用 Progress<T>
来处理进度:
notificationTask = Task.Factory.StartNew(async () => await ProcessNotifications(new Progress<BaseCoreNotification>(HandleNotificationProgress), notificationTaskCancellationSource),
notificationTaskCancellationSource.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default).Unwrap();
结果是进度还没有在主线程中处理。
切换到Task
s后发现问题。
在那种情况下,要报告进度,我需要使用 Progress<T>
。这个 class 应该在构造它的线程中调用委托,但反过来它只是从池中拿了一个线程。
原因是我的服务初始化(创建 Progress<T>
的地方)已经在 App
ctor 中完成,结果 没有 SynchronizationContext
那里(即SynchronizationContext.Current
为空)。在这种情况下 Progress<T>
简单地使用池中的线程并且 - 很可能 - BackgroundWorker
也这样做。
解决方案是将我的服务初始化移动到 Application_Startup
,其中主线程的 SynchronizationContext
已经可用。
我在使用 BackgroundWorker
时遇到问题。它似乎在与创建时不同的线程中调用其 Progress 事件,这与文档相反。一、供参考:
这是在创建 worker 时:
这是,当工人正在做它的工作时:
这是,当我的代码处理工作人员通过 ReportProgress
:
请注意,最后一个线程甚至与前两个(主线程和 BW 的工作线程)不同
工人是在App.xaml.cs中间接创建的:
public App()
{
Configuration.Configure(Container.Instance);
// (...)
// Configure core service
coreService = Container.Instance.Resolve<ICoreService>();
coreService.Init();
}
然后:
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 在 CoreService.Init()
中执行:
public void Init()
{
tfsService.Connect();
worker.RunWorkerAsync();
}
Worker 本身看起来更像这样:
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);
}
}
}
请注意,我在内部使用异步方法,但我明确地等待它们(因为无论如何我都在一个单独的线程中):
(reachedSince, recentBuilds) = tfsService.GetBuilds(lastBuild.Value).Result;
runningBuilds = tfsService.GetBuilds(runningBuildIds).Result;
plannedBuilds = tfsService.GetBuilds(plannedBuildIds).Result;
有什么我想念的吗?文档说,进度处理应该发生在与创建 BackgroundWorker
时相同的线程中。但事实并非如此,而且我显然在 UI.
应用程序是 WPF,但是它在后台运行,没有主程序 window,它的大部分工作是在 CoreService 中完成的,它只是偶尔显示 windows。
我该如何解决这个问题,以便 ReportProgress 运行 进入主线程?
Update:我修改了我的代码以使用 Task
s 代替并使用 Progress<T>
来处理进度:
notificationTask = Task.Factory.StartNew(async () => await ProcessNotifications(new Progress<BaseCoreNotification>(HandleNotificationProgress), notificationTaskCancellationSource),
notificationTaskCancellationSource.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default).Unwrap();
结果是进度还没有在主线程中处理。
切换到Task
s后发现问题。
在那种情况下,要报告进度,我需要使用 Progress<T>
。这个 class 应该在构造它的线程中调用委托,但反过来它只是从池中拿了一个线程。
原因是我的服务初始化(创建 Progress<T>
的地方)已经在 App
ctor 中完成,结果 没有 SynchronizationContext
那里(即SynchronizationContext.Current
为空)。在这种情况下 Progress<T>
简单地使用池中的线程并且 - 很可能 - BackgroundWorker
也这样做。
解决方案是将我的服务初始化移动到 Application_Startup
,其中主线程的 SynchronizationContext
已经可用。