Как остановить BackgroundWorker на закрытии формы события?


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

любые идеи, как я могу закрыть это приложения без исключения, или тупик?

Ниже приведены 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 65

12 ответов:

единственный безопасный и исключительный способ сделать это, который я знаю, - это фактически отменить событие 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...
}

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

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

и в каждом методе DoWork backgroundWorker сделать:

bgWorkesThreads.Add(Thread.CurrentThread);

артерия, которую вы можете использовать:

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

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

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

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

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

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

используя этот шаблон, вы должны быть в состоянии закрыть свою форму в любое время, даже если backgroundworker находится в середине чего-то (что может быть не очень хорошо, но это обязательно произойдет, поэтому с ним можно также справиться). Я не уверен, что это так необходимо, но вы можете полностью избавиться от фонового рабочего в событии 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

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

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();
}

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

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

Я полагаю, что это можно было бы изменить, запустив работника на "загруженный" обратный вызов, а не конструктор, но всего этого испытания можно избежать в целом, если используется механизм отчетности о ходе работы BackgroundWorker. Хорошо работает следующее:

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();
}

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

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

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

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

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

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

Это не будет работать для всех, но если вы делаете что-то в 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);
            }
    }

Я действительно не понимаю, почему DoEvents считается таким плохим выбором в этом случае, если вы используете это.включено = 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);

}

Я бы передал SynchronizationContext, связанный с текстовым полем, BackgroundWorker и использовал его для выполнения обновлений в потоке пользовательского интерфейса. Используя SynchronizationContext.Сообщение, вы можете проверить, если элемент управления утилизируется или утилизации.

а как же я.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

иначе:

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

одно решение, которое работает, но слишком сложно. Идея состоит в том, чтобы породить таймер, который будет продолжать пытаться закрыть форму, и форма откажется закрываться, пока не будет сказано bgWorker мертв.

private void HandleClosingEvent(object sender, CancelEventArgs e) {
    if (!this.bgWorker.IsBusy) {
        // bgWorker is dead, let Closing event proceed.
        e.Cancel = false;
        return;
    }
    if (!this.bgWorker.CancellationPending) {
        // it is first call to Closing, cancel the bgWorker.
        this.bgWorker.CancelAsync();
        this.timer1.Enabled = true;
    }
    // either this is first attempt to close the form, or bgWorker isn't dead.
    e.Cancel = true;
}

private void timer1_Tick(object sender, EventArgs e) {
    Trace.WriteLine("Trying to close...");
    Close();
}