Comment arrêter correctement BackgroundWorker
J'ai un formulaire avec 2 comboboxes dessus. Et je veux remplir combobox2.DataSource
basé sur combobox1.Text
et combobox2.Text
(je suppose que l'Utilisateur a complété la saisie dans combobox1
et est au milieu de la saisie dans combobox2
). J'ai donc un gestionnaire d'événements pour combobox2
comme ceci:
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
dans la mesure où construire DataSource est un processus qui prend du temps (il crée une requête à la base de données et l'exécute) j'ai décidé que c'est mieux l'exécuter dans un autre processus en utilisant BackgroundWorker. Il y a donc un scénario où cmbDataSourceExtractor n'a pas terminé son travail et l'utilisateur tape encore un symbole. Dans ce cas, je reçois une exception sur cette ligne
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
à propos de ce travailleur de fond est occupé et ne peut pas effectuer plusieurs actions en même temps.
Comment se débarrasser de cette exception?
Merci à l'avance!
8 réponses
CancelAsync
n'a pas réellement avorter votre fil ou quelque chose comme ça. Il envoie un message au worker thread que le travail doit être annulé via BackgroundWorker.CancellationPending
. Votre délégué DoWork qui est exécuté en arrière-plan doit périodiquement vérifier cette propriété et gérer l'Annulation elle-même.
la partie délicate est que votre délégué DoWork bloque probablement, ce qui signifie que le travail que vous faites sur votre source de données doit être terminé avant que vous puissiez faire quoi que ce soit d'autre (comme vérifier CancellationPending). Vous pouvez avoir besoin de déplacer votre travail réel à un autre délégué async (ou peut-être mieux encore, soumettre le travail au ThreadPool
), et avoir votre principal worker thread poll jusqu'à ce que ce thread de worker interne déclenche un État d'attente, ou il détecte Annulationpending.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx
http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
si vous ajoutez une boucle entre le CancelAsync () et le RunWorkerAsync () comme ça il résoudra votre problème
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
boucle while avec l'appel à L'Application.DoEvents () haulera l'exécution de votre nouveau thread worker jusqu'à ce que le thread actuel soit correctement annulé, gardez à l'esprit que vous devez toujours gérer l'annulation de votre thread worker. Avec quelque chose comme:
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
La Demande.DoEvents () dans le premier code snippet va continuer à traiter votre file d'attente de messages de threads GUI de sorte que le même pour annuler et mettre à jour le cmbDataSourceExtractor.La propriété IsBusy sera encore traitée (si vous avez simplement ajouté un continuer au lieu de L'Application.DoEvents () la boucle verrouillerait le thread GUI dans un État de busy et ne traiterait pas l'événement pour mettre à jour le cmbDataSourceExtractor.IsBusy)
vous devrez utiliser un drapeau partagé entre le thread principal et le BackgroundWorker, tel que BackgroundWorker.CancellationPending
. Quand vous voulez que le BackgroundWorker sorte, il suffit de mettre le drapeau en utilisant BackgroundWorker.CancelAsync ().
MSDN a un échantillon: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx
mon exemple . DoWork est ci-dessous:
DoLengthyWork();
//this is never executed
if(bgWorker.CancellationPending)
{
MessageBox.Show("Up to here? ...");
e.Cancel = true;
}
intérieur DoLenghtyWork:
public void DoLenghtyWork()
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
inside other stuff ():
public void OtherStuff()
{
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
ce que vous voulez faire est de modifier à la fois DoLenghtyWork et Other Stuff () afin qu'ils deviennent:
public void DoLenghtyWork()
{
if(!bgWorker.CancellationPending)
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
public void OtherStuff()
{
if(!bgWorker.CancellationPending)
{
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
le problème est causé par le fait que cmbDataSourceExtractor.CancelAsync()
est une méthode asynchrone, l'opération Cancel
n'a pas encore terminé quand cmdDataSourceExtractor.RunWorkerAsync(...)
existe. Vous devez attendre que cmdDataSourceExtractor
soit terminé avant d'appeler RunWorkerAsync
à nouveau. Comment faire cela est expliqué dans cette question de RS .
ma réponse est un peu différente parce que j'ai essayé ces méthodes mais elles n'ont pas fonctionné. Mon code utilise une classe supplémentaire qui vérifie un drapeau booléen dans une classe publique statique comme les valeurs de base de données sont lues ou où je le préfère juste avant qu'un objet soit ajouté à un objet de liste ou quelque chose comme tel. Voir le changement dans le code ci-dessous. J'ai ajouté le ThreadWatcher.Propriété StopThread. pour cette explication Je ne vais pas rétablir le fil courant parce que ce n'est pas votre problème mais c'est aussi facile comme définir la propriété à false avant d'accéder au thread suivant...
private void combobox2_TextChanged(object sender, EventArgs e)
{
//Stop the thread here with this
ThreadWatcher.StopThread = true;//the rest of this thread will run normally after the database function has stopped.
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
tout va bien
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
ajouter la classe suivante
public static class ThreadWatcher
{
public static bool StopThread { get; set; }
}
et dans votre classe où vous lisez la base de données
List<SomeObject>list = new List<SomeObject>();
...
if (!reader.IsDbNull(0))
something = reader.getString(0);
someobject = new someobject(something);
if (ThreadWatcher.StopThread == true)
break;
list.Add(something);
...
n'oubliez pas d'utiliser un bloc final pour fermer correctement votre connexion à la base de données, etc. Espérons que cette aide! S'il vous plaît Marquez-moi si vous le trouvez utile.
je suis d'accord avec les gars. Mais parfois, il faut ajouter plus de choses.
IE
1) ajouter ce worker.WorkerSupportsCancellation = true;
2) Ajoutez à votre catégorie une méthode pour faire les choses suivantes
public void KillMe()
{
worker.CancelAsync();
worker.Dispose();
worker = null;
GC.Collect();
}
donc avant de fermer votre application, vous devez appeler cette méthode.
3) probablement vous pouvez Dispose, null
toutes les variables et les minuteries qui sont à l'intérieur du BackgroundWorker
.
dans mon cas, j'ai dû mettre en commun la base de données pour confirmer le paiement et ensuite mettre à jour WPF
UI.
mécanisme qui renverse tous les processus:
public void Execute(object parameter)
{
try
{
var amount = ViewModel.Amount;
var transactionId = ViewModel.TransactionMain.TransactionId.ToString();
var productCode = ViewModel.TransactionMain.TransactionDetailList.First().Product.ProductCode;
var transactionReference = GetToken(amount, transactionId, productCode);
var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, transactionReference);
Process.Start(new ProcessStartInfo(url));
ViewModel.UpdateUiWhenDoneWithPayment = new BackgroundWorker {WorkerSupportsCancellation = true};
ViewModel.UpdateUiWhenDoneWithPayment.DoWork += ViewModel.updateUiWhenDoneWithPayment_DoWork;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerCompleted += ViewModel.updateUiWhenDoneWithPayment_RunWorkerCompleted;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerAsync();
}
catch (Exception e)
{
ViewModel.Log.Error("Failed to navigate to payments", e);
MessageBox.Show("Failed to navigate to payments");
}
}
mécanisme qui vérifie l'achèvement:
private void updateUiWhenDoneWithPayment_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(30000);
while (string.IsNullOrEmpty(GetAuthToken()) && !((BackgroundWorker)sender).CancellationPending)
{
Thread.Sleep(5000);
}
//Plug in pooling mechanism
this.AuthCode = GetAuthToken();
}
mécanisme qui annule si la fenêtre se ferme:
private void PaymentView_OnUnloaded(object sender, RoutedEventArgs e)
{
var context = DataContext as PaymentViewModel;
if (context.UpdateUiWhenDoneWithPayment != null && context.UpdateUiWhenDoneWithPayment.WorkerSupportsCancellation && context.UpdateUiWhenDoneWithPayment.IsBusy)
context.UpdateUiWhenDoneWithPayment.CancelAsync();
}