Как остановить BackgroundWorker на закрытии формы? - PullRequest
66 голосов
/ 13 ноября 2009

У меня есть форма, которая порождает BackgroundWorker, который должен обновить собственное текстовое поле формы (в главном потоке), следовательно, Invoke((Action) (...)); call.
Если в HandleClosingEvent я просто делаю bgWorker.CancelAsync(), тогда я получаю ObjectDisposedException на Invoke(...) звонке, понятно. Но если я сижу в HandleClosingEvent и жду выполнения bgWorker, то .Invoke (...) никогда не возвращается, также понятно.

Есть идеи, как закрыть это приложение, не получив исключение или тупик?

Ниже приведены 3 соответствующих метода простого класса Form1:

    public Form1() {
        InitializeComponent();
        Closing += HandleClosingEvent;
        this.bgWorker.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
        while (!this.bgWorker.CancellationPending) {
            Invoke((Action) (() => { this.textBox1.Text = Environment.TickCount.ToString(); }));
        }
    }

    private void HandleClosingEvent(object sender, CancelEventArgs e) {
        this.bgWorker.CancelAsync();
        /////// while (this.bgWorker.CancellationPending) {} // deadlock
    }

Ответы [ 12 ]

95 голосов
/ 14 ноября 2009

Единственный известный мне безопасный и исключительный способ сделать это - отменить событие FormClosing. Установите e.Cancel = true, если BGW все еще работает, и установите флаг, чтобы указать, что пользователь запросил закрытие. Затем проверьте этот флаг в обработчике событий RunWorkerCompleted BGW и вызовите Close (), если он установлен.

private bool closePending;

protected override void OnFormClosing(FormClosingEventArgs e) {
    if (backgroundWorker1.IsBusy) {
        closePending = true;
        backgroundWorker1.CancelAsync();
        e.Cancel = true;
        this.Enabled = false;   // or this.Hide()
        return;
    }
    base.OnFormClosing(e);
}

void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    if (closePending) this.Close();
    closePending = false;
    // etc...
}
3 голосов
/ 09 марта 2012

Я нашел другой способ. Если у вас есть больше backgroundWorkers, вы можете сделать:

List<Thread> bgWorkersThreads  = new List<Thread>();

и в каждом методе backgroundWorker's DoWork делают:

bgWorkesThreads.Add(Thread.CurrentThread);

Артер, который вы можете использовать:

foreach (Thread thread in this.bgWorkersThreads) 
{
     thread.Abort();    
}

Я использовал это в надстройке Word в Control, которую я использую в CustomTaskPane. Если кто-то закроет документ или приложение раньше, тогда все мои backgroundWorkes завершат свою работу, он вызовет COM Exception (точно не помню, какой). CancelAsync() не работает.

Но с этим я могу закрыть все потоки, которые используются backgroundworkers Немедленно в событии DocumentBeforeClose, и моя проблема решена.

2 голосов
/ 12 августа 2010

Вот мое решение (извините, оно в VB.Net).

Когда я запускаю событие FormClosing, я запускаю BackgroundWorker1.CancelAsync (), чтобы установить для параметра CancellationPending значение True. К сожалению, программа никогда не получает возможность проверить значение CancellationPending, чтобы установить для e.Cancel значение true (что, насколько я могу судить, можно сделать только в BackgroundWorker1_DoWork). Я не убрал эту строку, хотя, похоже, это не имеет значения.

Я добавил строку, которая установит для моей глобальной переменной bClosingForm значение True. Затем я добавил строку кода в свой BackgroundWorker_WorkCompleted, чтобы проверить как e.Cancelled, так и глобальную переменную bClosingForm перед выполнением любых завершающих шагов.

Используя этот шаблон, вы должны иметь возможность закрыть свою форму в любое время, даже если специалист по фону находится в середине чего-либо (что может быть нехорошо, но это обязательно произойдет, поэтому с ним также можно справиться) , Я не уверен, если это необходимо, но вы можете полностью удалить Фонового работника в событии Form_Closed после того, как все это произойдет.

Private bClosingForm As Boolean = False

Private Sub SomeFormName_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
    bClosingForm = True
    BackgroundWorker1.CancelAsync() 
End Sub

Private Sub backgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    'Run background tasks:
    If BackgroundWorker1.CancellationPending Then
        e.Cancel = True
    Else
        'Background work here
    End If
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    If Not bClosingForm Then
        If Not e.Cancelled Then
            'Completion Work here
        End If
    End If
End Sub
1 голос
/ 25 января 2010

Во-первых, исключение ObjectDisposedException является здесь только одной возможной ошибкой. Выполнение кода OP в большинстве случаев приводило к следующему исключению InvalidOperationException:

Invoke или BeginInvoke не могут быть вызваны на элементе управления, пока ручка окна был создан.

Полагаю, это можно исправить, запустив рабочий с обратным вызовом «Loaded», а не с помощью конструктора, но всего этого тяжелого испытания можно избежать, если использовать механизм создания отчетов BackgroundWorker's Progress. Хорошо работает следующее:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (!this.bgWorker.CancellationPending)
    {
        this.bgWorker.ReportProgress(Environment.TickCount);
        Thread.Sleep(1);
    }
}

private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.textBox1.Text = e.ProgressPercentage.ToString();
}

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

Интересно отметить, что удаление вышеуказанного спящего вызова приводит к засорению интерфейса пользователя, потреблению высокой загрузки ЦП и постоянно увеличивает использование памяти. Я думаю, это как-то связано с перегруженной очередью сообщений графического интерфейса. Тем не менее, при неактивном режиме ожидания загрузка ЦП практически равна нулю, и использование памяти тоже выглядит нормально. Чтобы быть осторожным, возможно, следует использовать более высокое значение, чем 1 мс? Мнение эксперта здесь будет оценено ... Обновление : Похоже, что пока обновление не слишком частое, оно должно быть в порядке: Ссылка

В любом случае я не могу предвидеть сценарий, в котором обновление GUI должно происходить с интервалами короче пары миллисекунд (по крайней мере, в тех случаях, когда человек наблюдает за GUI), поэтому я думаю, что большинство Отчет о прогрессе времени будет правильным выбором

1 голос
/ 14 ноября 2009

Не можете дождаться сигнала в деструкторе формы?

AutoResetEvent workerDone = new AutoResetEvent();

private void HandleClosingEvent(object sender, CancelEventArgs e)
{
    this.bgWorker.CancelAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    while (!this.bgWorker.CancellationPending) {
        Invoke((Action) (() => { this.textBox1.Text =   
                                 Environment.TickCount.ToString(); }));
    }
}


private ~Form1()
{
    workerDone.WaitOne();
}


void backgroundWorker1_RunWorkerCompleted( Object sender, RunWorkerCompletedEventArgs e )
{
    workerDone.Set();
}
0 голосов
/ 19 мая 2018

Я действительно не понимаю, почему DoEvents считается таким плохим выбором в этом случае, если вы используете this.enabled = false. Я думаю, что это сделало бы это довольно опрятным.

protected override void OnFormClosing(FormClosingEventArgs e) {

    this.Enabled = false;   // or this.Hide()
    e.Cancel = true;
    backgroundWorker1.CancelAsync();  

    while (backgroundWorker1.IsBusy) {

        Application.DoEvents();

    }

    e.cancel = false;
    base.OnFormClosing(e);

}
0 голосов
/ 18 декабря 2016

Это не будет работать для всех, но если вы делаете что-то в BackgroundWorker периодически, например, каждую секунду или каждые 10 секунд (возможно, опрос сервера), это работает хорошо, чтобы упорядоченно остановить процесс и без сообщений об ошибках (по крайней мере, пока) и за ними легко следить;

 public void StopPoll()
        {
            MyBackgroundWorker.CancelAsync(); //Cancel background worker
            AutoResetEvent1.Set(); //Release delay so cancellation occurs soon
        }

 private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!MyBackgroundWorker.CancellationPending)
            {
            //Do some background stuff
            MyBackgroundWorker.ReportProgress(0, (object)SomeData);
            AutoResetEvent1.WaitOne(10000);
            }
    }
0 голосов
/ 18 апреля 2016

Ваш фоновый работник не должен использовать Invoke для обновления текстового поля. Он должен попросить поток пользовательского интерфейса обновить текстовое поле с помощью события ProgressChanged, добавив значение для текстового поля.

Во время события Закрыто (или, возможно, Закрытие события) поток пользовательского интерфейса запоминает, что форма закрыта, прежде чем он отменяет фонового работника.

После получения progressChanged поток пользовательского интерфейса проверяет, закрыта ли форма, и только если нет, обновляет текстовое поле.

0 голосов
/ 01 августа 2013

Что насчет Me.IsHandleCreated?

    Private Sub BwDownload_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BwDownload.RunWorkerCompleted
    If Me.IsHandleCreated Then
        'Form is still open, so proceed
    End If
End Sub
0 голосов
/ 04 апреля 2012

Другой способ:

if (backgroundWorker.IsBusy)
{
    backgroundWorker.CancelAsync();
    while (backgroundWorker.IsBusy)
    {
        Application.DoEvents();
    }
}
...