1
votes

Quelle est la procédure appropriée pour alerter un thread d'arrêter ce qu'il fait et de revenir avant de quitter?

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:

  1. Définissez ThreadRunning = false de sorte que la prochaine fois que le thread T vérifie cette variable, il sait s'arrêter.
  2. Appelez mre.WaitOne () pour attendre que le thread T dise que c'est fait via lui en appelant mre.Set () .
  3. Si tel est le cas, débloquez et continuez, supprimez mre ( 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();
}


6 commentaires

new Thread est 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 :)


3 Réponses :



1
votes

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:

  • coopérative - code qui est exécuté par thread sait qu'il pourrait être annulé et que l'annulation est gérée avec grâce, c'est ce que vous essayez de faire ici.
  • impératif - forcer le thread à s'arrêter - appeler 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.


0 commentaires

0
votes

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


0 commentaires