2009-08-17 9 views
4

Comment synchroniser correctement? Pour l'instant, il est possible que SetData soit appelée après que e.WaitOne() a été complétée, alors d pourrait déjà être réglé sur une autre valeur. J'ai essayé d'insérer des verrous mais cela s'est soldé par un blocage.C# Problème de filetage avec AutoResetEvent

AutoResetEvent e = new AutoResetEvent(false); 

public SetData(MyData d) 
{ 
    this.d=d; 
    e.Set(); // notify that new data is available 
} 

// This runs in separate thread and waits for d to be set to a new value 
void Runner() 
{  
    while (true) 
    { 
     e.WaitOne(); // waits for new data to process 
     DoLongOperationWith_d(d); 
    } 
} 

Est-ce que la meilleure solution est d'introduire une nouvelle dataAlreadyBeenSetAndWaitingToBeProcessed booléenne variable définie dans SetData true et à la fin de DoLongOperationWith_d il pourrait être définie sur true, donc si SetData est appelée avec cette variable à true ça pourrait juste revenir?

Répondre

3

Ceci est non testé, mais est une façon élégante de le faire avec le.primitives à base net:

class Processor<T> { 
    Action<T> action; 
    Queue<T> queue = new Queue<T>(); 

    public Processor(Action<T> action) { 
     this.action = action; 
     new Thread(new ThreadStart(ThreadProc)).Start(); 
    } 

    public void Queue(T data) { 
     lock (queue) { 
      queue.Enqueue(data); 
      Monitor.Pulse(queue); 
     }    
    } 

    void ThreadProc() { 
     Monitor.Enter(queue); 
     Queue<T> copy; 

     while (true) {     
      if (queue.Count == 0) { 
       Monitor.Wait(queue); 
      } 

      copy = new Queue<T>(queue); 
      queue.Clear(); 
      Monitor.Exit(queue); 

      foreach (var item in copy) { 
       action(item); 
      } 

      Monitor.Enter(queue); 
     } 
    } 
} 

programme Exemple:

class Program { 

    static void Main(string[] args) { 

     Processor<int> p = new Processor<int>((data) => { Console.WriteLine(data); }); 
     p.Queue(1); 
     p.Queue(2); 

     Console.Read(); 

     p.Queue(3); 
    } 
} 

Ceci est une version non-file d'attente, une version de file d'attente peut être préférée:

object sync = new object(); 
AutoResetEvent e = new AutoResetEvent(false); 
bool pending = false; 

public SetData(MyData d) 
{ 
    lock(sync) 
    { 
     if (pending) throw(new CanNotSetDataException()); 

     this.d=d; 
     pending = true; 
    } 

    e.Set(); // notify that new data is available 
} 

void Runner() // this runs in separate thread and waits for d to be set to a new value 
{ 

    while (true) 
    { 

      e.WaitOne(); // waits for new data to process 
      DoLongOperationWith_d(d); 
      lock(sync) 
      { 
       pending = false; 
      } 
    } 
} 
+0

@ Spencer Ruport: Quoi? Si pending est défini sur true la première fois que SetData est appelée, il sera lancé une deuxième fois. Je suis sûr qu'il y a un moyen de le casser, mais je ne pense pas que ce soit avec la séquence que vous avez décrite. – Sean

+0

mais this.d ne peut pas être défini sauf si pending est false. –

+0

Mon mauvais. Je n'ai pas vu le 'if (pending)' là. –

2

Il existe deux scénarios potentiellement troublants ici.

1:

  • DoLongOperationWith_d (d) finitions.
  • SetData() est appelée, en stockant une nouvelle valeur dans d.
  • e.WaitOne() est appelé, mais étant donné qu'une valeur a déjà été définie, le thread attend toujours.

Si c'est votre préoccupation, je pense que vous pouvez vous détendre. De l'documentation, nous voyons que

Si un thread appelle WaitOne alors que le AutoResetEvent est dans l'état signalé, le fil ne bloque pas. L'AutoResetEvent libère le thread immédiatement et retourne à l'état non signalé.

Donc ce n'est pas un problème. Toutefois, en fonction de comment et quand est appelé SetData(), vous pouvez avoir affaire à la plus grave

2:

  • SetData() est appelée, le stockage d'une nouvelle valeur dans d et se réveiller le coureur .
  • DoLongOperationWith_d (d) démarre.
  • SetData() est appelé à nouveau, en stockant une nouvelle valeur dans d.
  • SetData() est appelé à nouveau! L'ancienne valeur de d est perdue pour toujours; DoLongOperationWith_d() ne sera jamais invoqué dessus.

Si c'est votre problème, le moyen le plus simple de le résoudre est une file d'attente concurrente. Les implémentations abondent.

1

Vous pouvez utiliser 2 événements,

AutoResetEvent e = new AutoResetEvent(false); 
AutoResetEvent readyForMore = new AutoResetEvent(true); // Initially signaled 

public SetData(MyData d) 
{ 
    // This will immediately determine if readyForMore is set or not. 
    if(readyForMore.WaitOne(0,true)) { 
    this.d=d; 
    e.Set(); // notify that new data is available 
    } 
    // you could return a bool or something to indicate it bailed. 
} 

void Runner() // this runs in separate thread and waits for d to be set to a new value 
{ 

    while (true) 
    { 

      e.WaitOne(); // waits for new data to process 
      DoLongOperationWith_d(d); 
      readyForMore.Set(); 
    } 
} 

Une des choses vous pouvez faire avec cette approche est SetData prendre un délai d'attente, et le transmettre en WaitOne. Je pense cependant que vous devriez enquêter ThreadPool.QueueUserWorkItem.

+0

Le problème avec ceci est que SetData sera bloqué dès qu'il commence le traitement. –

+0

La file d'attente est évidemment supérieure. J'ai juste essayé de répondre à la question exactement comme demandé. –