为啥 BackgroundWorker 的 Progress 事件运行在不同的线程上?

Posted

技术标签:

【中文标题】为啥 BackgroundWorker 的 Progress 事件运行在不同的线程上?【英文标题】:Why BackgroundWorker's Progress event runs on a different thread than it should?为什么 BackgroundWorker 的 Progress 事件运行在不同的线程上? 【发布时间】:2021-11-25 06:07:19 【问题描述】:

我在使用 BackgroundWorker 时遇到问题。与文档相反,它似乎在与创建它的线程不同的线程中调用其 Progress 事件。首先,供参考:

这是在创建工人时:

这是,当工人在做它的工作时:

这是,当我的代码处理工作人员通过ReportProgress 报告的进度时:

注意,最后一个线程甚至与前两个不同(主线程和 BW 的工作线程)

worker 是在 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;

我有什么遗漏吗? Docs 说,进度处理应该发生在与创建BackgroundWorker 时相同的线程中。但事实并非如此,而且我显然在 UI 上遇到了错误。

应用程序是 WPF,但是它在后台工作,没有主窗口,它的大部分工作都在 CoreService 中完成,它只是偶尔显示窗口。

我该如何解决这个问题,以便 ReportProgress 将在主线程中运行?


更新:我修改了我的代码以使用Tasks 并使用Progress&lt;T&gt; 来处理进度:

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

结果是主线程中仍未处理进度

【问题讨论】:

RunWorkerAsync 方法在哪里调用? @TheodorZoulias,在 CoreService.Init() 中,在从 DC 解决它之前排在前面。 引用this 答案:BackgroundWorker 在调用RunWorkerAsync() 的线程的当前SynchronizationContext 上引发ProgressChanged 事件。 附带说明,在 async-await 技术出现之后,BackgroundWorker 类已呈现为 technologically obsolete。 通常应该从(WinForms 应用程序的)UI 线程创建后台工作程序。这通常直接从向导中完成。由于您的情况似乎并非如此,那么您可能使用了错误的工具来完成这项工作(或者至少您使用不正确)。 【参考方案1】:

切换到Tasks后发现问题。

在这种情况下,要报告进度,我需要使用 Progress&lt;T&gt;。这个类应该在构造它的线程中调用委托,但反过来它只是从池中获取一个线程。

原因是我的服务的初始化(在 Progress&lt;T&gt; 创建的位置)已经在 App ctor 中完成,结果是,那里没有 SynchronizationContext(即, SynchronizationContext.Current 为空)。在这种情况下,Progress&lt;T&gt; 只是使用池中的线程,而且 - 很可能 - BackgroundWorker 也这样做。

解决方案是将我的服务的初始化移动到Application_Startup,其中主线程的SynchronizationContext 已经可用。

【讨论】:

以上是关于为啥 BackgroundWorker 的 Progress 事件运行在不同的线程上?的主要内容,如果未能解决你的问题,请参考以下文章

为啥相同的代码在我的 BackGroundWorker 线程中比在我的 GUI 线程中慢得多?

为啥surface pro wifi传输速度这么慢

mac pro 装了idk 8 为啥终端里面没有

Flash Pro cc 为啥我手机上的舞台宽度和高度设置为Flash pro里面的?

C# form发起backgroundworker 当form close时 backgroundworker 还会继续工作吗

为啥我没有收到“跨线程操作无效”错误