Jak zatrzymać BackgroundWorker na wydarzeniu zamykającym formularz?

Mam formularz, który wywołuje BackgroundWorker, który powinien zaktualizować własne pole tekstowe formularza (w głównym wątku), stąd wywołanie Invoke((Action) (...));.
Jeśli w HandleClosingEvent po prostu robię bgWorker.CancelAsync(), to dostaję ObjectDisposedException na Invoke(...) połączenie, zrozumiałe. Ale jeśli siedzę w HandleClosingEvent i czekam na bgWorker do zrobienia, to .Invoke(...) nigdy nie wraca, również zrozumiale.

Jakieś pomysły jak zamknąć tę aplikację bez uzyskania wyjątku lub impasu?

Poniżej znajdują się 3 odpowiednie metody prostego Formula1 Klasa:

    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
    }
Author: Hamish Smith, 2009-11-13

12 answers

Jedynym bezpiecznym i wyjątkowym sposobem na to, jaki znam, jest anulowanie zdarzenia FormClosing. Ustaw e. Cancel = true, jeśli BGW jest nadal uruchomiony i ustaw flagę, aby wskazać, że użytkownik zażądał zamknięcia. Następnie sprawdź ten znacznik w obsłudze zdarzenia RUNWORKERCOMPLETED BGW i wywołaj Close (), jeśli jest ustawiony.

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...
}
 102
Author: Hans Passant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-10 08:12:11

Znalazłem inny sposób. Jeśli masz więcej backgroundworkerów możesz zrobić:

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

I w każdej metodzie DoWork wykonaj:

bgWorkesThreads.Add(Thread.CurrentThread);

Arter, którego możesz użyć:

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

Użyłem tego w Word Add - in in Control, którego używam w CustomTaskPane. Jeśli ktoś wcześniej zamknie dokument lub aplikację, to wszystkie moje backgroundworks skończą swoją pracę, podnosi to jakieś COM Exception (nie pamiętam dokładnie, które).Nie działa.

Ale dzięki temu mogę zamknąć wszystkie wątki które są używane przez backgroundworkers natychmiast w zdarzeniu DocumentBeforeClose i mój problem został rozwiązany.

 3
Author: Bronix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-05 02:01:09

Oto moje rozwiązanie (Przepraszam, że jest w VB.Net).

Kiedy uruchamiam Zdarzenie FormClosing uruchamiam BackgroundWorker1.CancelAsync (), aby ustawić wartość CancellationPending na True. Niestety, program nigdy tak naprawdę nie ma możliwości sprawdzenia wartości CancellationPending value, aby ustawić e. Cancel na true (co z tego co wiem, Można zrobić tylko w BackgroundWorker1_DoWork). Nie usunąłem tej linii, chociaż nie wydaje się to robić różnicy.

Dodałem wiersz, który ustawia moją globalną zmienną, bClosingForm, na True. Następnie dodałem wiersz kodu w moim BackgroundWorker_WorkCompleted, aby sprawdzić zarówno e. anulowane, jak i globalną zmienną, bClosingForm, przed wykonaniem jakichkolwiek kroków końcowych.

Korzystając z tego szablonu, powinieneś być w stanie zamknąć swój formularz w dowolnym momencie, nawet jeśli pracownik tła jest w trakcie czegoś (co może nie być dobre, ale na pewno się wydarzy, więc równie dobrze można sobie z tym poradzić). Nie wiem, czy to konieczne, ale możesz pozbyć się workera tła w całości w zdarzeniu Form_Closed po tym wszystkim.

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
Author: Tim Hagel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-26 06:16:12

Nie możesz czekać na sygnał w destruktorze formy?

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
Author: Cheeso,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-13 21:01:06

Po pierwsze, ObjectDisposedException jest tu tylko jedną możliwą pułapką. Po uruchomieniu kodu OP wywołano następujące InvalidOperationException w znaczącej liczbie przypadków:

Nie można wywołać Invoke lub BeginInvoke na sterowaniu aż do klamki okna został stworzony.

Przypuszczam, że można to zmienić poprzez uruchomienie workera na "załadowanym" wywołaniu zwrotnym, a nie konstruktora, ale całej tej gehenny można całkowicie uniknąć, jeśli Wykorzystywany jest mechanizm raportowania postępów w pracy. Następujące działa dobrze:

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

Przejąłem parametr procentowy, ale można użyć drugiego przeciążenia, aby przekazać dowolny parametr.

Warto zauważyć, że usunięcie powyższego wywołania sleep zatyka interfejs użytkownika, zużywa wysoki procesor i stale zwiększa zużycie pamięci. Myślę, że ma to coś wspólnego z kolejką komunikatów GUI jest przeciążony. Jednak przy nienaruszonym wywołaniu sleep użycie procesora jest praktycznie 0 i zużycie pamięci też wydaje się w porządku. Aby być ostrożnym, być może należy użyć wartości wyższej niż 1 ms? Opinia eksperta tutaj będzie mile widziana... Update: wygląda na to, że dopóki aktualizacja nie jest zbyt częsta, powinno być OK: Link

W każdym razie nie mogę przewidzieć scenariusza, w którym aktualizacja GUI musi być w odstępach krótszych niż kilka milisekund( przynajmniej w scenariuszach, w których człowiek obserwuje GUI), więc myślę, że większość raportowanie postępów w czasie byłoby właściwym wyborem

 1
Author: Ohad Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:13

Naprawdę nie rozumiem, dlaczego DoEvents jest uważany za taki zły wybór w tym przypadku, jeśli używasz tego.enabled = false. Myślę, że byłoby całkiem schludnie.

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

}
 1
Author: Peter Jamsmenson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-18 23:57:36

Twój backgroundworker nie powinien używać Invoke do aktualizacji pola tekstowego. Powinien poprosić wątek UI o aktualizację textboxa za pomocą event ProgressChanged z wartością, którą należy umieścić w dołączonym textboxie.

Podczas zamknięcia zdarzenia (a może zamknięcia zdarzenia), wątek interfejsu użytkownika pamięta, że formularz jest zamknięty przed anulowaniem backgroundworker.

Po otrzymaniu progressChanged wątek UI sprawdza czy formularz jest zamknięty i tylko jeśli nie, aktualizuje pole tekstowe.

 0
Author: Harald Coppoolse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-18 13:45:38

To nie będzie działać dla wszystkich, ale jeśli robisz coś w BackgroundWorker okresowo, jak co sekundę lub co 10 sekund, (być może ankieta na serwerze) to wydaje się działać dobrze, aby zatrzymać proces w uporządkowany sposób i bez komunikatów o błędach (przynajmniej do tej pory) i jest łatwe do naśladowania; {]}

 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
Author: SnowMan50,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-18 12:09:06

Chciałbym przekazać SynchronizationContext skojarzony z textbox do BackgroundWorker i użyć go do wykonywania aktualizacji w wątku UI. Korzystanie Z SynchronizationContext.Post, możesz sprawdzić, czy kontrola jest usuwana lub wyrzucana.

 -1
Author: Hasani Blackwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-13 19:48:08

A co ze mną?Ishandle został stworzony?

    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
 -1
Author: Luke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-01 10:48:15

Inny sposób:

if (backgroundWorker.IsBusy)
{
    backgroundWorker.CancelAsync();
    while (backgroundWorker.IsBusy)
    {
        Application.DoEvents();
    }
}
 -2
Author: Kergorian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-04 18:17:10

Jedno rozwiązanie, które działa, ale zbyt skomplikowane. Chodzi o to, aby wyzwolić czasomierz, który będzie próbował zamknąć formularz, a forma odmówi zamknięcia, dopóki said {1]} nie umrze.

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
Author: THX-1138,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-05 02:03:02