如何停止 WPF 中的后台工作人员?

Posted

技术标签:

【中文标题】如何停止 WPF 中的后台工作人员?【英文标题】:How to stop background worker in WPF? 【发布时间】:2021-12-23 23:41:04 【问题描述】:

我在我的 WPF 应用程序中使用 MVVM 模型。我有一个绑定到取消按钮的命令。我有一个启动按钮,可以启动一些后台工作人员。当我单击取消按钮时,我希望所有后台工作人员停止/退出。 当我点击取消按钮时,使用我当前的代码,后台工作人员不会停止并且“StartEngineeringOperation”完成。谁能帮我解决我在这里做错了什么?

当前代码:

对于 EngineeringViewModel.cs:

公共类 EngineeringViewModel

public EngineeringViewModel()

            StartEngineering= new DelegateCommand(o =>
            
                worker = new BackgroundWorker
                
                    WorkerReportsProgress = true,
                    WorkerSupportsCancellation = true
                ;
                worker.ProgressChanged += Worker_ProgressChanged;
                worker.RunWorkerCompleted += worker_RunWorkerCompleted;
                if (worker.IsBusy != true) worker.RunWorkerAsync();
                worker.DoWork += (s, e) =>
                
                    StartEngineeringOperation();
                    if (worker.CancellationPending)
                    
                        e.Cancel = true;
                        return;
                    
                ;
            ,
                k => true);
            Cancel = new DelegateCommand(CancelEngineeringOperation);


private void StartEngineeringOperation()
   
      startAlarmService();
      startTrendQualityCheck();
   

private void CancelEngineeringOperation(object param)
               
        worker.DoWork += (s, e) =>
        
            if (worker.IsBusy)
            
                worker.CancelAsync();
                e.Cancel = true;
                return;
            
           
        ;
       
    

我试过这个: 但似乎不起作用:

private void StartEngineeringOperation()
   
      startAlarmService();                                                                                                      
           if (worker.CancellationPending)
            
                e.Cancel = true;
                return;
            
      startTrendQualityCheck();
   

【问题讨论】:

您不能只是“取消”任意代码。您的 StartEngineeringOperation 必须手动检查是否请求取消并(优雅地)停止它正在做的事情。 考虑使用async await Asynchronous programming 而不是后台工作人员。还有CancellationToken用于取消多线程操作。 Felix 已经指出 BackgroundWorkers 已过时,您应该使用 Task.Runasyncawait 即使后台工作人员可能已经过时 - 使用带有取消令牌的任务或其他任何东西都不能解决一般问题。 相关:How to stop BackgroundWorker correctly 【参考方案1】:

正如您可能从 te cmets 中了解到的,您需要在您想要支持取消的操作中轮询 BackgroundWorker 的状态。然后采取措施优雅地取消正在进行的操作。

该示例显示了如何在单击按钮时取消后台线程。第一个示例使用旧的BackgroundWorker,第二个示例使用现代且更简洁的任务库。

后台工作人员

private BackgroundWorker Worker  get; set; 

private void StartWorker()

  this.Worker = new BackgroundWorker
  
    WorkerReportsProgress = true,
    WorkerSupportsCancellation = true
  ;
   
  this.Worker.DoWork += BackgroundWorker_DoWork;


private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)

  BackgroundWorker worker = sender as BackgroundWorker;

  DoCancellableWork();  

  // Stop BackgroundWorker from executing
  if (worker.CancellationPending)
  
    e.Cancel = true;
       


private void DoCancellableWork()
      
  // Check for cancellation before executing the cancellable operation and allocating resources etc..
  if (this.Worker.CancellationPending)
  
    return;
  

  // Periodically/regularly check for the cancellation flag
  for (int i = 0; i <= 10000000000; i++)
  
    if (this.Worker.CancellationPending)
    
      // Cancel operation gracefully e.g., do some cleanup, free resources etc.

      return;
    

    // Do some work
  


// Alternatively use a command e.g., in a view model class
private void CancelBackgroundWorker_Click(object sender, EventArgs e)

  if (this.Worker.WorkerSupportsCancellation)
  
    this.Worker.CancelAsync();
  

任务库

该示例使用Progress<T> 从后台线程向 UI 线程报告进度。

private CancellationTokenSource CancellationTokenSource  get; set; 

private async Task StartWorker()

  this.CancellationTokenSource = new CancellationTokenSource();

  // Prepare callback to update UI from the background thread.
  // The Progress<T> instance MUST be created on the UI thread
  IProgress<int> progressReporter = new Progress<int>(progress => this.ProgressBar.Value = progress);

  await Task.Run(
    () => DoWork(progressReporter, this.CancellationTokenSource.Token), 
    this.CancellationTokenSource.Token);

  this.CancellationTokenSource.Dispose();


private void DoWork(IProgress<int> progressReporter, CancellationToken cancellationToken)

  DoCancellableWork(progressReporter, cancellationToken);


private void DoCancellableWork(IProgress<int> progressReporter, CancellationToken cancellationToken)

  // Check for cancellation before executing the operation and allocating resources etc..
  if (cancellationToken.IsCancellationRequested)
  
    return;
  

  // Periodically/regularly check for the cancellation flag
  for (int i = 0; i <= 10000000000; i++)
  
    if (cancellationToken.IsCancellationRequested)
    
      // Cancel operation gracefully e.g., do some cleanup, free resources etc.

      return;
    

    // Do some work

    // Report progress
    progressReporter.Report(20);
  


// Alternatively use a command e.g., in a view model class
private void CancelBackgroundThread_Click(object sender, EventArgs e)

  this.CancellationtokenSource?.Cancel();

【讨论】:

感谢@BionicCode 的回答。但我的 CancelableWork 不需要定期进行。所以使用 for 循环是行不通的。我需要在工作开始和完成之间的任何时间取消工作,具体取决于单击取消按钮。要做的工作不是循环的。如何在不使用循环的情况下轮询“IsCancellationRequested”? 这与循环无关。这就是为什么我在循环上方的代码注释中写了“定期/定期检查...”。 2) before 调用另一个长时间运行的操作(而长时间运行的操作会自己执行有用的检查,以便在长时间运行的操作开始后它可以自行取消) .这意味着您必须决定何时检查 CancellationPending。 3) 可能在持久化数据之前(写入文件、数据库等)。 4) 在创建结果或更新 UI 之前。所有这些考虑因素都适用于被调用的每个方法(调用树)。【参考方案2】:

由于 OP 将正在完成的任务描述为“检查服务”,我假设完成的工作如下所示:

while(true)
    // check service
    // Post result back to UI thread
    Thread.Sleep(...);

这不是编写此类检查的最佳方式。在大多数使用 Thread.Sleep 的情况下,计时器会是更好的选择:

var myTimer  = new System.Timers.Timer(...);
myTimer .Elapsed += OnTimedEvent;
myTimer .AutoReset = true;
myTimer .Enabled = true;

...

private void OnTimedEvent(Object source, ElapsedEventArgs e)

    // check service
    // Post result back to UI thread

这使得停止/启动正在完成的任务的问题变得简单,只需更改计时器的启用标志即可。也可以使用计时器或同步上下文直接在 UI 线程上运行事件,如果“检查服务”只需要几毫秒,这可能是最好的解决方案。

【讨论】:

以上是关于如何停止 WPF 中的后台工作人员?的主要内容,如果未能解决你的问题,请参考以下文章

后台服务中的 Task.Run 意外停止工作

用户使用 GridSplitter 后 WPF 行高绑定停止工作

用于 RibbonTab 的 WPF IsSelected 绑定在 ctrl+tab 上停止工作

WPF:在后台不断更新 UI

知道啥会导致 Visual Studio 2013 中的“vshost32.exe 已停止工作”吗?

让 Android 应用程序在后台运行,防止它停止/死亡