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

时间:2018-11-08 11:47:00

标签: c# winforms checkbox backgroundworker

我的WinForm应用程序中的背景工作人员遇到了问题。 这是我的情况: 我有一个以OnLoad表单事件开始的后台工作人员。然后,我在表单上有一个复选框来停止/启动工作人员。 当我取消选中该框时,事件将调用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永远不会结束。

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

1 个答案:

答案 0 :(得分:1)

首先,BGW已过时,已完全由async/await,Tasks和Progress<T>取代。任务允许合成,连续和取消,这对于BGW而言相当复杂。我怀疑BGW完成后,bwMBExitEvent事件用于实现 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;
}