后台工作者不会在 c# WinForm 应用程序中收到 CancellationPending

Background Worker won't receive CancellationPending in c# WinForm application

我的 WinForm 应用程序中的 backgroundworkers 有问题。 这是我的场景: 我有一个在 OnLoad 表单事件中启动的后台工作人员。然后我在表格上有一个复选框 stop/start 工人。 当我取消选中该框时,事件调用 cancelAsync() 方法,但工作人员没有收到 CancellationPending。 为了调试这个问题,我尝试在窗体上添加一个与 CheckedChanged 事件执行相同操作的按钮,在这种情况下它有效吗???!!!

这是我的代码片段:

工人们...

private void BwMB_DoWork(object sender, DoWorkEventArgs e)
{
    bwMBExitEvent.Reset();

    bool loop = true;
    while (loop)
    {
        if (bwMB.CancellationPending)
        {
            loop = false;
        }

        ... other code ...
    }

    e.Cancel = true;
    bwMBExitEvent.Set();
}

CheckedChanged 事件 ...

private void checkBoxModBus_CheckedChanged(object sender, EventArgs e)
{
    try
    {   
        if (checkBoxModBus.Checked)
        {
            if (!bwMB.IsBusy)
                bwMB.RunWorkerAsync();
        }
        else
        {
            if (bwMB.IsBusy)
            {
                bwMB.CancelAsync();
                bwMBExitEvent.WaitOne();
            }
        }
     }
     catch(Exception ex)
     {
        Console.WriteLine(ex.Message);
     }
}

以及用于调试的按钮点击事件...

private void button2_Click(object sender, EventArgs e)
{
    bwMB.CancelAsync();
    bwMBExitEvent.WaitOne();
}

当我点击按钮时,工作人员收到取消信号并退出循环设置 bwMBExitEvent (ManualResetEvent)。这样点击事件WaitOne结束等待。 当我取消选中工人复选框时,停止 运行,但没有收到信号,所以不要结束循环并且没有设置事件。 CheckedChanged 的​​ WaitOne 永远不会结束。

请原谅任何英语语法或拼写问题。

首先,BGW 已经过时,完全被 async/await、Tasks 和 Progress<T> 取代。任务允许组合、延续和取消,这对于 BGW 来说是相当复杂的。我怀疑 bwMBExitEvent 事件用于在 BGW 完成后实现 continuation

文章 Async in 4.5: Enabling Progress and Cancellation in Async APIs 解释了取消和进度报告在 .NET 4.5 及更高版本(即所有支持的版本)中的工作原理。

也就是说,BGW 取消没有问题。我怀疑该事件,loop 变量和其他非种子代码最终导致竞争条件。

虽然使用 2、4 或 10 个可取消任务而不是 BGW 很容易。

启动多个任务很容易:

private void StartTasks()
{
   _cts=new CancellationTokenSource();
  //Start each method passing a CancellationToken
   _tasks=new[]{
                 Task.Run(()=>WorkerMethod1(_cts.Token)),
                 Task.Run(()=>WorkerMethod2(_cts.Token)),
                  ...
               };
   //Enable the Cancel button
   Cancel.Enabled=true;
}

此代码创建 N 个任务并将它们存储在一个数组中。它还创建了一个 new CancellationTokenSource,可用于向所有任务或线程发送取消信号并监视其令牌

通过按钮调用取消任务 CancellationTokenSource.Cancel() 并等待所有任务完成:

private async void Cancel_Clicked(object sender,EventArgs args)
{
    if (_cts!=null)
    {
       lblStatus.Text = "Cancelling";
       //Signal a cancellation
        _cts.Cancel();
       //Asynchronously wait for all tasks to finish
        await Task.WhenAll(_tasks);
        _cts=null;           
       lblStatus.Text = "Cancelled";
    }
    //Disable the button
    Cancel.Enabled=false;
}

通过使用 async/await,处理程序在等待任务完成时不会阻塞。它也不需要 InvokeBeginInvoke,因为在 await.

之后在 UI 线程上恢复执行

worker 方法所要做的就是检查 CancellationToken.IsCancellationRequested 标志:

private void WorkerMethod1(CancellationToken token)
{
    //If cancellation isn't requested
    while(!token.IsCancellationRequested)
    {
        //Loop one more time
    }
}

把所有东西放在一起 :

//Hold active tasks
Task[] _tasks;

private void WorkerMethod1(CancellationToken token)
{
    //If cancellation isn't requested
    while(!token.IsCancellationRequested)
    {
        //Loop one more time
    }
}

CancellationTokenSource _cts;

private void OnLoad(...)
{
    //Fire the tasks
    StartTasks();
}

private void StartTasks()
{
   _cts=new CancellationTokenSource();
  //Start each method passing a CancellationToken
   _tasks=new[]{
                 Task.Run(()=>WorkerMethod1(_cts.Token)),
                 Task.Run(()=>WorkerMethod2(_cts.Token)),
                  ...
               };
   //Enable the Cancel button
   Cancel.Enabled=true;
}

private async void Cancel_Clicked(object sender,EventArgs args)
{
    if (_cts!=null)
    {
       //Signal a cancellation
        _cts.Cancel();
       //Asynchronously wait for all tasks to finish
        await Task.WhenAll(_tasks);
        _cts=null;           
    }
    //Disable the button
    Cancel.Enabled=false;
}