Фоновый рабочий не получит CancellationPending в приложении c # WinForm - PullRequest
0 голосов
/ 08 ноября 2018

У меня проблема с фоновыми работниками в моем приложении 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). Таким образом, событие click WaitOne завершает ожидание Когда я снимаю флажок с рабочего, прекращаю работу, но не получаю сигнал, поэтому не завершаю цикл, и событие не устанавливается. WaitOne у CheckedChanged никогда не заканчивается.

Прошу прощения за любые грамматические или орфографические проблемы.

1 Ответ

0 голосов
/ 08 ноября 2018

Прежде всего, BGW устарел, полностью заменен на async/await, Задачи и Progress<T>. Задачи позволяют составлять, продолжения и отмены, что довольно сложно с BGW. Я подозреваю, что событие bwMBExitEvent используется для реализации продолжения после завершения BGW.

В статье Асинхронизация в 4.5: Включение хода выполнения и отмены в API-интерфейсах Async объясняется, как работают отмены и отчеты о ходе выполнения в .NET 4.5 и более поздних версиях (т. Е. Во всех поддерживаемых версиях).

Тем не менее, BGW не имеет проблем с отменой. Я приостанавливаю событие, loop переменные и другой невидимый код в конечном итоге вызывают условия гонки.

Использование 2, 4 или 10 отменяемых задач вместо BGW, хотя это легко.

  • Несколько задач можно легко запустить с помощью Task.Run .
  • Можно ожидать завершения нескольких задач без блокировки с помощью Task.WhenAll .
  • Отмена может сигнализироваться потокам, задачам при асинхронных операциях с помощью CancellationTokenSource

Легко запустить несколько задач:

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 задач и сохраняет их в массиве. Он также создает новый 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 обработчик не блокируется в ожидании завершения задач. Ему не нужны Invoke или BeginInvoke, так как выполнение возобновляется в потоке пользовательского интерфейса после await.

Все рабочие методы должны проверять 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;
}
...