Как остановить BackgroundWorker на событии Closing Формы?

Вместо того макроса, мог бы я предлагать этого:

template<typename T, int N>
inline size_t array_size(T(&)[N])
{
    return N;
}

#define ARRAY_SIZE(X)   (sizeof(array_size(X)) ? (sizeof(X) / sizeof((X)[0])) : -1)

1) Мы хотим использовать макрос для создания его временем компиляции постоянный; результатом вызова функции не является постоянное время компиляции.

2) Однако мы не хотим использовать макрос, потому что макрос мог случайно использоваться на указателе. Функция может только использоваться на массивах времени компиляции.

Так, мы используем определенный мыс функции для создания макроса "безопасным"; если функция существует (т.е. она имеет ненулевой размер), тогда, мы используем макрос как выше. Если функция не существует, мы возвращаем плохое значение.

66
задан Hamish Smith 13 November 2009 в 21:03
поделиться

6 ответов

Единственный известный мне способ сделать это без взаимоблокировок и исключений - это отменить событие FormClosing. Установите e.Cancel = true, если BGW все еще работает, и установите флаг, указывающий, что пользователь запросил закрытие. Затем проверьте этот флаг в обработчике событий BGW RunWorkerCompleted и вызовите 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...
}
96
ответ дан 24 November 2019 в 15:05
поделиться

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

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();
}
1
ответ дан 24 November 2019 в 15:05
поделиться

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

-1
ответ дан 24 November 2019 в 15:05
поделиться

Одно решение, которое работает, но слишком сложно. Идея состоит в том, чтобы запустить таймер, который будет продолжать попытки закрыть форму, и форма не будет закрываться до тех пор, пока 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();
}
-2
ответ дан 24 November 2019 в 15:05
поделиться

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

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

Я полагаю, что это можно было бы исправить, запустив работника на 'Загруженном' обратном вызове, а не на конструкторе, но этого полного испытания можно было бы полностью избежать, если бы использовался механизм отчётности 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();
}

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

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

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

.
1
ответ дан 24 November 2019 в 15:05
поделиться

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

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

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

Используя этот шаблон, вы должны иметь возможность закрыть свою форму в любое время, даже если фоновый работник находится в середине чего-то (что может быть не очень хорошо, но это обязательно произойдет, так что с этим можно также иметь дело) .Я не уверен, что это необходимо, но вы можете полностью удалить Background worker в событии 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
2
ответ дан 24 November 2019 в 15:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: