75 votes

Comment arrêter BackgroundWorker sur le Formulaire de Clôture de l'événement?

J'ai un formulaire qui génère un BackgroundWorker, qui devrait formulaire de mise à jour propre zone de texte (sur le thread principal), donc d' Invoke((Action) (...)); appel.
Si, en HandleClosingEvent je viens de faire bgWorker.CancelAsync() puis-je obtenir de l' ObjectDisposedException sur Invoke(...) appel, ce qui est compréhensible. Mais si je suis assis en HandleClosingEvent et d'attendre bgWorker à faire, que .Invoke(...) ne retourne jamais, aussi naturellement.

Des idées comment puis-je fermer cette application sans exception, ou de l'impasse?

Suivant 3 méthodes pertinentes de la simple classe 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
    }

103voto

Hans Passant Points 475940

Le seul blocage-fort et d'exception-safe façon de faire ce que je sais, c'est effectivement annuler la FormClosing événement. Ensemble e.Cancel = true si le BGW est toujours en cours et de définir un indicateur pour indiquer que l'utilisateur a demandé à un proche. Ensuite, vérifiez que l'indicateur dans le BGW du gestionnaire d'événements RunWorkerCompleted et appeler Close() si elle est définie.

protected override void OnFormClosing(FormClosingEventArgs e) {
    if (!mCompleted) {
        backgroundWorker1.CancelAsync();
        this.Enabled = false;
        e.Cancel = true;
        mClosePending = true;
        return;
    }
    base.OnFormClosing(e);
}

void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    mCompleted = true;
    if (mClosePending) this.Close();
}

2voto

Tim Hagel Points 21

C'était ma solution (Désolé c'est en VB.Net). Quand je lance le FormClosing cas je exécuter BackgroundWorker1.CancelAsync() pour définir la CancellationPending de la valeur à True. Malheureusement, le programme ne reçoit jamais vraiment un chancel pour vérifier la valeur CancellationPending valeur à l'ensemble e.Annuler pour vrai (qui, autant que je peux dire, ne peut être fait dans BackgroundWorker1_DoWork). Je n'ai pas de supprimer cette ligne, bien qu'il ne semble pas vraiment faire une différence, mais je ne l'ai ajouter dans une ligne qui aurait la valeur de ma variable globale, bClosingForm, à Vrai. Ensuite, j'ai ajouté une ligne de code dans mon BackgroundWorker_WorkCompleted pour vérifier à la fois.e.Annulée ainsi que la variable globale, bClosingForm, avant de procéder à toute fin étapes. À l'aide de ce modèle, vous devriez être en mesure de fermer votre formulaire, à tout moment, même si le backgroundworker est au milieu de quelque chose (ce qui peut ne pas être bon, mais c'est lié à arriver, donc il pourrait aussi bien être traité avec). Je ne suis pas sûr si c'est nécessaire, mais vous pourriez disposer le Fond de travailleur entièrement dans le Form_Closed événement après cela a lieu.

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

2voto

Bronix Points 11

J'ai trouvé un autre moyen. Si vous avez plus de backgroundWorkers vous pouvez faire:

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

et dans tous les backgroundWorker est DoWork la méthode:

bgWorkesThreads.Add(Thread.CurrentThread);

Arter que vous pouvez utiliser:

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

J'ai utilisé ce dans Word Add-in de Contrôle, que j'utilise en CustomTaskPane. Si quelqu'un fermer le document ou l'application plus tôt, puis toutes mes backgroundWorkes termine son travail, elle soulève un certain COM Exception(je ne me souviens pas exatly qui).CancelAsync() ne fonctionne pas.

Mais avec cela, je peux fermer tous les threads qui sont utilisées par l' backgroundworkers Immédiatement en DocumentBeforeClose événement et mon problème est résolu.

1voto

Cheeso Points 87022

Pouvez-vous sans attendre sur le signal dans le destructeur de la forme?

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

1voto

Ohad Schneider Points 10485

Tout d'abord, la ObjectDisposedException n'est qu'un écueil possible ici. L'exécution de l'OP du code a produit les InvalidOperationException sur un nombre important d'occasions:

Appeler ou BeginInvoke ne peut pas être appelé sur un contrôle jusqu'à ce que la poignée de la fenêtre a été créé.

Je suppose que cela pourrait être modifiée par le départ du travailleur sur le "Chargé" de rappel plutôt que le constructeur, mais toute cette épreuve peut être évité complètement si BackgroundWorker les Progrès de mécanisme de contrôle est utilisé. Les ouvrages suivants:

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

J'ai un peu détourné le pourcentage de paramètre, mais on peut utiliser l'autre surcharge de passer n'importe quel paramètre.

Il est intéressant de noter que la suppression de la au-dessus de dormir appeler les sabots de l'INTERFACE utilisateur, consomme du CPU élevée et augmente continuellement l'utilisation de la mémoire. Je suppose que cela a quelque chose à voir avec le message de la file d'attente de l'interface graphique qui est surchargé. Cependant, avec le sommeil d'appel intacte, l'utilisation du PROCESSEUR est pratiquement 0 et l'utilisation de la mémoire semble bien, trop. Pour être prudent, peut-être une valeur supérieure à 1 ms doit être utilisé? Un avis d'expert ici serait appréciée... mise à Jour: Il semble que tant que la mise à jour n'est pas trop fréquent, il devrait être OK: Lien

En tout cas, je ne peux pas imaginer un scénario où la mise à jour de l'interface graphique doit être à des intervalles de moins de quelques millisecondes (au moins, dans les cas où un homme est en regardant l'interface graphique), donc je pense que la plupart du temps, les rapports sur les progrès serait le bon choix

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X