web-dev-qa-db-fra.com

Y at-il un moyen de mettre indéfiniment en pause un fil?

Pendant mon temps libre, je travaillais sur une application .NET d'analyse de sites Web. L'une des fonctionnalités de cette application que je voulais inclure était un bouton de pause permettant de mettre en pause un fil de discussion spécifique.

Je suis relativement nouveau en multi-threading et je n'ai pas été en mesure de trouver un moyen de mettre indéfiniment en pause un thread actuellement pris en charge. Je ne me souviens pas de la classe/méthode exacte, mais je sais qu'il existe un moyen de le faire, mais cela a été signalé comme obsolète par le framework .NET.

Existe-t-il un moyen générique de mettre en pause indéfiniment un thread de travail dans C # .NET.

Je n'ai pas eu beaucoup de temps ces derniers temps pour travailler sur cette application et la dernière fois que je l'ai touchée, c'était dans le framework .NET 2.0. Je suis ouvert à toutes les nouvelles fonctionnalités (le cas échéant) existantes dans le framework .NET 3.5, mais j'aimerais savoir quelle solution fonctionne également dans le framework 2.0, car c'est ce que j'utilise au travail et il serait bon de le faire. savoir juste au cas où.

32
Dan Herbert

Jamais, jamais, utilisez Thread.Suspend. Le problème majeur avec ce logiciel est que 99% du temps, vous ne pouvez pas savoir ce que fait le thread lorsque vous le suspendez. Si ce fil contient un verrou, vous simplifiez la tâche, etc. Gardez à l'esprit que le code que vous appelez peut acquérir/libérer des verrous en coulisse. Win32 a une API similaire: SuspendThread et ResumeThread. Les documents suivants pour SuspendThread résument bien les dangers de l’API:

http://msdn.Microsoft.com/en-us/library/ms686345(VS.85).aspx

Cette fonction est principalement conçue pour être utilisée par les débogueurs. Il n'est pas destiné à être utilisé pour la synchronisation des threads. L'appel de SuspendThread sur un thread possédant un objet de synchronisation, tel qu'un mutex ou une section critique, peut entraîner un blocage si le thread appelant tente d'obtenir un objet de synchronisation appartenant à un thread suspendu. Pour éviter cette situation, un thread au sein d'une application qui n'est pas un débogueur doit signaler à l'autre thread de se suspendre. Le thread cible doit être conçu pour surveiller ce signal et répondre de manière appropriée.

La bonne façon de suspendre un thread indéfiniment consiste à utiliser un ManualResetEvent. Le fil est probablement en boucle, effectuant un travail. Le moyen le plus simple de suspendre le thread est de le faire "vérifier" l'événement à chaque itération, comme suit:

while (true)
{
    _suspendEvent.WaitOne(Timeout.Infinite);

    // Do some work...
}

Vous spécifiez un délai infini pour que, lorsque l'événement n'est pas signalé, le thread se bloque indéfiniment, jusqu'à ce que l'événement soit signalé à quel point le thread reprendra là où il l'avait laissé.

Vous créeriez l'événement comme suit:

ManualResetEvent _suspendEvent = new ManualResetEvent(true);

Le paramètre true indique à l'événement de commencer dans l'état signalé.

Lorsque vous souhaitez suspendre le fil, procédez comme suit:

_suspendEvent.Reset();

Et pour reprendre le fil:

_suspendEvent.Set();

Vous pouvez utiliser un mécanisme similaire pour indiquer au thread de quitter et d'attendre les deux événements, en détectant quel événement a été signalé.

Juste pour le plaisir, je vais vous donner un exemple complet:

public class Worker
{
    ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
    ManualResetEvent _pauseEvent = new ManualResetEvent(true);
    Thread _thread;

    public Worker() { }

    public void Start()
    {
        _thread = new Thread(DoWork);
        _thread.Start();
    }

    public void Pause()
    {
        _pauseEvent.Reset();
    }

    public void Resume()
    {
        _pauseEvent.Set();
    }

    public void Stop()
    {
        // Signal the shutdown event
        _shutdownEvent.Set();

        // Make sure to resume any paused threads
        _pauseEvent.Set();

        // Wait for the thread to exit
        _thread.Join();
    }

    public void DoWork()
    {
        while (true)
        {
            _pauseEvent.WaitOne(Timeout.Infinite);

            if (_shutdownEvent.WaitOne(0))
                break;

            // Do the work here..
        }
    }
}
91
Brannon

Le Threading en C # ebook résume Thread.Suspend et Thread.Resume ainsi:

Les méthodes obsolètes Suspend et Resume ont deux modes - dangereux et inutile!

Le livre recommande l’utilisation d’une structure de synchronisation telle que AutoResetEvent ou Monitor.Wait pour suspendre et reprendre le fil.

15
Shabbyrobe

Je viens d'implémenter une classe LoopingThread qui boucle une action transmise au constructeur. Il est basé sur le post de Brannon. J'ai mis d'autres éléments dans cela comme WaitForPause(), WaitForStop() et une propriété TimeBetween, qui indique le temps qui devrait être attendu avant la prochaine boucle.

J'ai aussi décidé de changer la boucle while en une boucle do-while. Cela nous donnera un comportement déterministe pour une Start() et une Pause() successives. Par déterministe, je veux dire que l’action est exécutée au moins une fois après une commande Start(). Dans la mise en œuvre de Brannon, cela pourrait ne pas être le cas.

J'ai omis certaines choses pour la racine de la question. Des choses comme "vérifier si le fil a déjà été démarré", ou le modèle IDisposable.

public class LoopingThread
{
  private readonly Action _loopedAction;
  private readonly AutoResetEvent _pauseEvent;
  private readonly AutoResetEvent _resumeEvent;
  private readonly AutoResetEvent _stopEvent;
  private readonly AutoResetEvent _waitEvent;

  private readonly Thread _thread;

  public LoopingThread (Action loopedAction)
  {
    _loopedAction = loopedAction;
    _thread = new Thread (Loop);
    _pauseEvent = new AutoResetEvent (false);
    _resumeEvent = new AutoResetEvent (false);
    _stopEvent = new AutoResetEvent (false);
    _waitEvent = new AutoResetEvent (false);
  }

  public void Start ()
  {
    _thread.Start();
  }

  public void Pause (int timeout = 0)
  {
    _pauseEvent.Set();
    _waitEvent.WaitOne (timeout);
  }

  public void Resume ()
  {
    _resumeEvent.Set ();
  }

  public void Stop (int timeout = 0)
  {
    _stopEvent.Set();
    _resumeEvent.Set();
    _thread.Join (timeout);
  }

  public void WaitForPause ()
  {
    Pause (Timeout.Infinite);
  }

  public void WaitForStop ()
  {
    Stop (Timeout.Infinite);
  }

  public int PauseBetween { get; set; }

  private void Loop ()
  {
    do
    {
      _loopedAction ();

      if (_pauseEvent.WaitOne (PauseBetween))
      {
        _waitEvent.Set ();
        _resumeEvent.WaitOne (Timeout.Infinite);
      }
    } while (!_stopEvent.WaitOne (0));
  }
}
1
Matthias

En accord avec ce que les autres ont dit - ne le faites pas. Ce que vous voulez vraiment faire est de "suspendre le travail" et de laisser vos discussions se déplacer librement. Pouvez-vous nous donner plus de détails sur le ou les fils que vous souhaitez suspendre? Si vous n'avez pas démarré le fil, vous ne devriez même pas envisager de le suspendre - ce n'est pas à vous. Si c'est votre fil, alors je suggère au lieu de le suspendre, vous l'avez juste assis, attendant plus de travail à faire. Brannon a d'excellentes suggestions pour cette option dans sa réponse. Sinon, laissez-le finir. et lancez-en un nouveau quand vous en avez besoin.

0
Bruce

S'il n'y a pas d'exigences de synchronisation:

Thread.Sleep(Timeout.Infinite);

0
Alberto

Outre les suggestions ci-dessus, j'aimerais ajouter un conseil. Dans certains cas, BackgroundWorker peut simplifier votre code (en particulier lorsque vous utilisez une méthode anonyme pour définir DoWork et ses autres événements).

0
Lex Li