Quelle est la procédure appropriée pour alerter un thread en cours d'exécution d'arrêter ce qu'il fait et de revenir avant de quitter l'application?
protected Thread T;
protected static ManualResetEvent mre = new ManualResetEvent(false);
protected static bool ThreadRunning = true;
public Form1()
{
InitializeComponent();
T = new Thread(ThreadFunc);
T.Start();
}
private void ThreadFunc()
{
while (ThreadRunning)
{
// Do stuff
Thread.Sleep(40);
}
mre.WaitOne();
mre.Close();
}
private void ExitButton_Click(object sender, EventArgs e)
{
ThreadRunning = false;
mre.Set();
Application.Exit();
}
Au départ, j'avais mon code configuré comme ci-dessus. Ma réflexion sur la façon dont la sortie est correctement est la suivante:
ThreadRunning = false de sorte que la prochaine fois que le thread T vérifie cette variable, il sait s'arrêter. mre.WaitOne () pour attendre que le thread T dise que c'est fait via lui en appelant mre.Set () . mre.Close () ) et quittez. Pour une raison quelconque, la configuration ci-dessus échoue parfois après avoir cliqué sur le bouton de sortie et tout le formulaire devient inactif.
Ma nouvelle configuration est ci-dessous mais ne me semble pas tout à fait correcte, par exemple, mre.Set () n'attendra rien et Application.Exit () est immédiatement après. J'attends juste qu'il échoue comme avant, mais jusqu'à présent, ce n'est pas le cas.
protected Thread T;
protected static ManualResetEvent mre = new ManualResetEvent(false);
protected static bool ThreadRunning = true;
public Form1()
{
InitializeComponent();
T = new Thread(ThreadFunc);
T.Start();
}
private void ThreadFunc()
{
while (ThreadRunning)
{
// Do stuff
Thread.Sleep(40);
}
mre.Set();
}
private void ExitButton_Click(object sender, EventArgs e)
{
ThreadRunning = false;
mre.WaitOne();
mre.Close();
Application.Exit();
}
3 Réponses :
Vous pouvez utiliser le même modèle de variable conditionnelle en utilisant Thread.IsAlive comme ceci:
} }
private void ThreadFunc()
{
while ( !CancelRequired )
{
// Do stuff
Thread.Sleep(40);
}
}
private void ButtonAction_Click(object sender, EventArgs e)
{
CancelRequired = true;
while ( TheThread.IsAlive )
Application.DoEvents();
Application.Exit();
}
XXX
public FormTest()
{
InitializeComponent();
TheThread = new Thread(ThreadFunc);
TheThread.Start();
}
vous n'avez pas besoin de locker ici et il n'y a aucun sens à utiliser volatile pour cela. Au lieu de cela, vous devez utiliser volatile pour CancelRequired en raison de l'optimisation potentielle compilée lorsque sa valeur sera mise en cache dans le CPU et que le changement ne sera pas propagé à un autre thread qui est exécuté par un autre coeur.
En effet, à peine réveillé pour ne pas dormir ... je fais n'importe quoi. Je ne sais pas pourquoi j'ai mis une serrure ... probablement un réflexe pavlovien. juste volatile pour être scrupuleux. J'essaierai de me rendormir. maux ...
Nommer votre champ Thread est une mauvaise idée. Comment allez-vous accéder aux méthodes statiques de la classe Thread alors?
Vous ne vous trompez pas sur cette opinion. Certains considèrent que cette utilisation n'est pas propre. Il n'y a aucun problème avec cela. Réponse mise à jour.
L'interface utilisateur se bloque car vous bloquez le thread d'interface utilisateur avec mre.WaitOne (); .
Si vous devez attendre la fin du thread, vous pouvez utiliser son IsAlive et traiter les messages d'application, vous n'avez pas besoin d'événements d'application pour cela:
while(_t.IsAlive) Application.DoEvents();
Il y a 2 approches d'annulation de thread:
Thread.Abort ou Interrupt , ne l'utilisez pas . Comme @HansPassant l'a mentionné, bool n'est pas la meilleure option car ce compilateur peut l'optimiser et la valeur booléenne pourrait être mise en cache et sa modification ne peut pas être gérée par un thread en boucle. Vous devez le rendre au moins volatil ou simplement refactoriser le code pour utiliser CancellationSource.
Compte tenu de ce que fait votre thread, peut-être BackgroundWorker , Timer ou le modèle Producteur / Consommateur est une meilleure alternative à Thread , mais j'ai trop peu de contexte pour recommander quoi que ce soit. De plus, cela ne fonctionne bien que si vous n'avez qu'une seule instance de Form1 dans l'application, si vous avez une application multiforme et que l'utilisateur peut ouvrir plusieurs formulaires Form1 , vous aurez des problèmes.
Recommandation générale, si vous pouvez travailler avec des champs au niveau de l'instance, veuillez le faire, n'utilisez pas de statique.
Attendre 40 ms entre chaque tâche ne pose aucun problème, mais que faire si vous deviez attendre 5 secondes ou plus? Ensuite, annuler entre chaque attente serait problématique, et la bonne chose à faire serait d'annuler l'attente elle-même . Il est assez facile de le faire en fait. Remplacez simplement Thread.Sleep (40) par Task.Delay (40, token) .Wait () où token est un CancellationToken .
class Form1 : Form
{
protected readonly CancellationTokenSource _cts;
protected readonly Task _task;
public Form1()
{
InitializeComponent();
_cts = new CancellationTokenSource();
_task = Task.Run(TaskFunc);
this.FormClosing += Form_FormClosing;
}
private async Task TaskFunc()
{
try
{
while (true)
{
// Do async stuff here, using _cts.Token if possible
// The stuff will run in thread-pool threads
await Task.Delay(40, _cts.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// Ignore cancellation exception
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
this.Close();
}
private async void Form_FormClosing(object sender, FormClosingEventArgs e)
{
if (_task == null || _task.IsCompleted) return;
e.Cancel = true;
_cts.Cancel();
this.Visible = false; // Or give feedback that the form is closing
var completedTask = await Task.WhenAny(_task, Task.Delay(5000));
if (completedTask != _task) Debug.WriteLine("Task refuses to die");
_task = null;
await Task.Yield(); // To ensure that Close won't be called synchronously
this.Close(); // After await we are back in the UI thread
}
}
new Threadest pour les threads "fire and forget" que vous n'annulez pas, ne récupérez pas d'informations ou ne vous inquiétez pas s'ils sont en cours d'exécution. Si vous souhaitez récupérer des informations ou annuler le fil de discussion, vous devez utiliser async / await .Regardez ici stackoverflow.com/questions / 57144771 /… .dans la dernière réponse, votre question a répondu
Un booléen n'est pas un objet de synchronisation de thread, comme ManualResetEvent. "Certaines circonstances" sont la version Release de votre application, permettant d'optimiser la lecture de la variable booléenne. Il existe plusieurs façons de résoudre ce problème, comme l'utilisation de MRE au lieu d'un booléen, mais cela a tendance à aider à améliorer l'interaction des threads. Comment faire cela correctement pour BackgroundWorker est le sujet de ce Q + R < / a>.
@Nux, avez-vous réussi à résoudre un problème?
@fenixil Oui, j'ai pris en considération votre réponse et celle d'OlivierRogier.
@Nux, cette ressource est axée sur la communauté, alors n'hésitez pas à voter pour les réponses qui vous sont utiles :)