2010-06-28 4 views
1

Je voudrais exécuter du code alternativement, afin que je puisse arrêter l'exécution à tout moment. Ce code est-il sûr?Est-ce un moyen sûr d'exécuter des threads en alternance?

static class Program 
{ 
    static void Main() 
    { 
     var foo = new Foo(); 
     //wait for interaction (this will be GUI app, so eg. btnNext_click) 
     foo.Continue(); 
     //wait again etc. 
     foo.Continue(); 
     foo.Continue(); 
     foo.Continue(); 
     foo.Continue(); 
     foo.Continue(); 
    } 
} 

class Foo 
{ 
    public Foo() 
    { 
     new Thread(Run).Start(); 
    } 

    private void Run() 
    { 
     Break(); 
     OnRun(); 
    } 

    protected virtual void OnRun() 
    { 
     for (var i = 0; i < 5; i++) 
     { 
      Console.WriteLine(i); 
      Break(); 
     } 
     //do something else and break; 
    } 

    private void Break() 
    { 
     lock (this) 
     { 
      Monitor.Pulse(this); 
      Monitor.Wait(this); 
     } 
    } 

    public void Continue() 
    { 
     lock (this) 
     { 
      Monitor.Pulse(this); 
      Monitor.Wait(this); 
     } 
    } 
} 

Bien sûr, je sais, que maintenant l'application ne finira jamais, mais ce n'est pas le point. J'ai besoin de cela, car je voudrais présenter les étapes d'une sorte d'algorithme et décrire ce qui se passe à un moment particulier, et faire tout dans un thread conduirait à de nombreuses complications, même en utilisant une petite quantité de boucles dans le code. Par exemple ces lignes:

for (var i = 0; i < 5; i++) 
{ 
    Console.WriteLine(i); 
    Break(); 
} 

devrait être ensuite remplacé par:

if (this.i < 5) 
{ 
    Console.WriteLine(i++); 
} 

Et c'est juste un petit exemple de ce que je veux présenter. Le code sera plus compliqué qu'une boucle for factice.

+0

C'est bizarre. N'utilisez pas de thread. –

+0

@Hans Passant: Comment feriez-vous ce genre de ... self explaining (?) Code? A l'intérieur de 'OnRun' je voudrais exécuter des algorithmes liés aux graphiques et montrer l'explication de l'utilisateur de ce qui se passe dans ce moment, par exemple.avec des messages et des vertices/bords de coloration. Diviser une telle chose en petits morceaux serait pénible à coder. – prostynick

+0

La manière dont vous avez formulé la question semble indiquer que vous n'êtes pas familier avec le modèle de threads d'interface utilisateur pour Windows Forms. Vous devriez expliquer ce qui ne peut pas être fait avec un événement qui doit "alterner" avec un thread d'arrière-plan séparé. – David

Répondre

1

Je vous recommande de vérifier cette blog post sur la mise en œuvre des fibres.

code(Dans le cas où le site va vers le bas.)

public class Fiber 
{ 
    private readonly Stack<IEnumerator> stackFrame = new Stack<IEnumerator>(); 
    private IEnumerator currentRoutine; 

    public Fiber(IEnumerator entryPoint) 
    { 
     this.currentRoutine = entryPoint; 
    } 

    public bool Step() 
    { 
     if (currentRoutine.MoveNext()) 
     { 
      var subRoutine = currentRoutine.Current 
          as IEnumerator; 
      if (subRoutine != null) 
      { 
       stackFrame.Push(currentRoutine); 
       currentRoutine = subRoutine; 
      } 
     } 
     else if (stackFrame.Count > 0) 
     { 
      currentRoutine = stackFrame.Pop(); 
     } 
     else 
     { 
      OnFiberTerminated(
       new FiberTerminatedEventArgs(
        currentRoutine.Current 
      ) 
     ); 
      return false; 
     } 

     return true; 
    } 

    public event EventHandler<FiberTerminatedEventArgs> FiberTerminated; 

    private void OnFiberTerminated(FiberTerminatedEventArgs e) 
    { 
     var handler = FiberTerminated; 
     if (handler != null) 
     { 
      handler(this, e); 
     } 
    } 
} 

public class FiberTerminatedEventArgs : EventArgs 
{ 
    private readonly object result; 

    public FiberTerminatedEventArgs(object result) 
    { 
     this.result = result; 
    } 

    public object Result 
    { 
     get { return this.result; } 
    } 
} 

class FiberTest 
{ 
    private static IEnumerator Recurse(int n) 
    { 
     Console.WriteLine(n); 
     yield return n; 
     if (n > 0) 
     { 
      yield return Recurse(n - 1); 
     } 
    } 

    static void Main(string[] args) 
    { 
     var fiber = new Fiber(Recurse(5)); 
     while (fiber.Step()) ; 
    } 
} 
+0

Merci beaucoup pour votre lien. Cela m'aide beaucoup. Je n'ai pas vraiment besoin de classe entière, du moins maintenant. Cependant, l'idée d'utiliser les mots-clés «Iterator» et «yield» pour rompre (pause) est merveilleuse. Salutations – prostynick

+0

@prostynick - Pas de problème, bonne chance avec votre travail. – ChaosPandion

1

« ... ce sera l'application GUI ... »

Ensuite, vous ne voulez probablement pas et ne sera pas avoir le code séquentiel comme ci-dessus dans Main().

I.e. le thread principal de l'interface graphique n'exécutera pas un code série comme ci-dessus, mais sera généralement inactif, repeint, etc. ou manipulant le bouton Continue.
Dans ce gestionnaire d'événements, vous pouvez utiliser un Auto|ManualResetEvent pour signaler au travailleur de continuer.
Dans l'agent, attendez simplement l'événement.

+0

Merci. C'est tellement évident, mais comme l'a souligné @Hans Passant, c'est un peu bizarre, et d'une manière ou d'une autre je n'ai pas pensé à utiliser juste 'AutoResetEvent'. – prostynick

+0

@prostynick: vous êtes les bienvenus. –

0

je suggère que chaque fois que l'on considère en utilisant Monitor.Wait(), on devrait écrire du code afin qu'il fonctionne correctement si le Wait parfois a agi spontanément comme s'il avait reçu une impulsion. En règle générale, cela signifie que l'on doit utiliser le modèle:

lock(monitorObj) 
{ 
    while(notYetReady) 
    Monitor.Wait(monitorObj); 
} 

Pour votre scénario, je vous suggère de faire quelque chose comme:

lock(monitorObj) 
{ 
    turn = [[identifier for this "thread"]]; 
    Monitor.PulseAll(monitorObj); 
    while(turn != [[identifier for this "thread"]]) 
    Monitor.Wait(monitorObj); 
} 

Il est impossible pour turn de changer entre son cours de vérification que ce soit le tour du fil en cours pour continuer et le Monitor.Wait. Ainsi, si le Wait n'est pas sauté, le PulseAll est garanti pour le réveiller. Notez que le code fonctionnerait très bien si Wait agissait spontanément comme s'il recevait une impulsion - il tournerait simplement, observerait que turn n'était pas défini pour le thread en cours, et retournerait en attente.

Questions connexes