2009-06-23 30 views
15
public void MyTest() 
{ 
    bool eventFinished = false; 

    myEventRaiser.OnEvent += delegate { doStuff(); eventFinished = true; }; 
    myEventRaiser.RaiseEventInSeperateThread() 

    while(!eventFinished) Thread.Sleep(1); 

    Assert.That(stuff); 
} 

Perché l'evento finito non può essere volatile e importa?perché una variabile locale non può essere volatile in C#?

Mi sembra che in questo caso il compilatore o il runtime possa diventare intelligente per il suo bene e 'sapere' nel ciclo while che eventFinished può essere solo falso. Soprattutto se si considera il modo in cui una variabile sollevata viene generata come membro di una classe e il delegato come un metodo di quella stessa classe e in tal modo si privano le ottimizzazioni del fatto che eventFinished era una volta una variabile locale.

+2

La variabile non è locale in questo caso! Piuttosto, è una variabile di istanza in una classe generata dal compilatore. –

+4

Questa è una differenza semantica: in termini di codice, è una variabile locale che viene catturata ... –

+1

Quello che vedo è una variabile locale che viene aggiornata in un thread diverso. E anche se so che il compilatore creerà una variabile di istanza della mia variabile locale, il pre-compilatore apparentemente non è o è troppo testardo per riconoscerlo. –

risposta

12

Esiste una primitiva di threading, ManualResetEvent per eseguire esattamente questa operazione: non si desidera utilizzare un flag booleano.

Qualcosa del genere dovrebbe fare il lavoro:

public void MyTest() 
{ 
    var doneEvent = new ManualResetEvent(false); 

    myEventRaiser.OnEvent += delegate { doStuff(); doneEvent.Set(); }; 
    myEventRaiser.RaiseEventInSeparateThread(); 
    doneEvent.WaitOne(); 

    Assert.That(stuff); 
} 

Per quanto riguarda la mancanza di supporto per la parola volatile sulle variabili locali, non credo ci sia alcuna ragione per cui questo potrebbe non essere in teoria possibile in C#. Molto probabilmente, non è supportato semplicemente perché non c'era alcun uso per tale funzionalità prima di C# 2.0. Ora, con l'esistenza di metodi anonimi e funzioni lambda, tale supporto potrebbe diventare potenzialmente utile. Qualcuno si prega di chiarire le questioni se mi manca qualcosa qui.

+2

Dov'è Eric quando hai bisogno di lui? ;-p –

+0

Hehe. In effetti, stiamo entrando in aree oscure di C#/CLR ora - su cui sono sicuro che potrebbe far luce. – Noldorin

+0

Grazie per il ManualResetEvent. Funziona anche quando l'evento viene chiamato nello stesso thread, che è una possibilità che non volevo escludere in MyTest(). –

1

Cosa accadrebbe se l'evento generato non fosse stato completato fino a quando il processo non fosse uscito dall'ambito di quella variabile locale? La variabile sarebbe stata rilasciata e il tuo thread sarebbe fallito.

L'approccio ragionevole consiste nell'associare una funzione delegato che indica al thread padre che il sottoprocesso ha completato.

+0

Il codice è corretto; questa è una variabile "catturata" e viene implementata come un campo in una classe generata dal compilatore. Il thread non fallirà. –

+0

Non potrebbe essere lo stesso caso con una variabile a livello di classe, però? Se l'istanza viene raccolta prima della fine dell'evento nell'altro thread, si verifica lo stesso problema. – Noldorin

+0

Non può essere sottoposto a garbage collection mentre è ancora nell'area di visibilità visibile di entrambi i thread. –

10

In nella maggior parte degli scenari, le variabili locali sono specifiche di un thread, pertanto i problemi associati a volatile non sono necessari.

Questo cambia quando, come nel tuo esempio, è una variabile "catturata" - quando viene implementata silenziosamente come un campo in una classe generata dal compilatore. Quindi, in teoria, potrebbe essere essere volatile, ma nella maggior parte dei casi non varrebbe la complessità aggiuntiva.

In particolare, qualcosa come un Monitor (alias lock) con Pulse ecc. Poteva farlo altrettanto bene, come qualsiasi altro numero di altri costrutti di threading.

Threading è difficile, e un loop attiva raramente è il modo migliore per gestirlo ...


Re di modifica ... secondThread.Join() sarebbe la cosa più ovvia - ma se si vuole veramente utilizzare un token separato, vedi sotto. Il vantaggio di questo (su cose come ManualResetEvent) è che non richiede nulla dal sistema operativo - è gestito esclusivamente all'interno della CLI.

using System; 
using System.Threading; 
static class Program { 
    static void WriteLine(string message) { 
     Console.WriteLine(Thread.CurrentThread.Name + ": " + message); 
    } 
    static void Main() { 
     Thread.CurrentThread.Name = "Main"; 
     object syncLock = new object(); 
     Thread thread = new Thread(DoStuff); 
     thread.Name = "DoStuff"; 
     lock (syncLock) { 
      WriteLine("starting second thread"); 
      thread.Start(syncLock); 
      Monitor.Wait(syncLock); 
     } 
     WriteLine("exiting"); 
    } 
    static void DoStuff(object lockHandle) { 
     WriteLine("entered"); 

     for (int i = 0; i < 10; i++) { 
      Thread.Sleep(500); 
      WriteLine("working..."); 
     } 
     lock (lockHandle) { 
      Monitor.Pulse(lockHandle); 
     } 
     WriteLine("exiting"); 
    } 
} 
+0

Cosa succede se RaiseEventInSeperateThread() è stato effettivamente implementato come: new Thread (() => {Thread.sleep (100); OnEvent();}; Come useresti un Monitor o un lock per avere MyTest() attendere che il delegato dell'evento finisca di essere in esecuzione? –

+0

Voglio dire: nuovo Thread (() => {Thread.sleep (100); OnEvent();}). Start(); –

+0

Avresti il ​​thread thread WaitOne e il altro Pulse. Proverò ad aggiungere un esempio più tardi ... –

4

Si potrebbe anche usare Voltile.Write se si vuole fare la var locale si comportano come volatile. Come in:

public void MyTest() 
{ 
    bool eventFinished = false; 

    myEventRaiser.OnEvent += delegate { doStuff(); Volatile.Write(ref eventFinished, true); }; 
    myEventRaiser.RaiseEventInSeperateThread() 

    while(!Volatile.Read(eventFinished)) Thread.Sleep(1); 

    Assert.That(stuff); 
} 
+1

buona risposta ma il tuo ciclo 'while' dovrebbe usare' Volatile.Read' – CoderBrien

Problemi correlati