2015-09-18 5 views
11

Was wäre das asynchrone (erwartete) Äquivalent von AutoResetEvent?Akzeptiert AutoResetEvent

Wenn im klassischen Thread-Synchronisierung würden wir so etwas wie folgt verwenden:

AutoResetEvent signal = new AutoResetEvent(false); 

    void Thread1Proc() 
    { 
     //do some stuff 
     //.. 
     //.. 

     signal.WaitOne(); //wait for an outer thread to signal we are good to continue 

     //do some more stuff 
     //.. 
     //.. 
    } 

    void Thread2Proc() 
    { 
     //do some stuff 
     //.. 
     //.. 

     signal.Set(); //signal the other thread it's good to go 

     //do some more stuff 
     //.. 
     //.. 
    } 

Ich war in der neuen Asynchron-Art, die Dinge zu tun, um so etwas zu sein, würde der Hoffnung, dass:

SomeAsyncAutoResetEvent asyncSignal = new SomeAsyncAutoResetEvent(); 

async void Task1Proc() 
{ 
    //do some stuff 
    //.. 
    //.. 

    await asyncSignal.WaitOne(); //wait for an outer thread to signal we are good to continue 

    //do some more stuff 
    //.. 
    //.. 
} 

async void Task2Proc() 
{ 
    //do some stuff 
    //.. 
    //.. 

    asyncSignal.Set(); //signal the other thread it's good to go 

    //do some more stuff 
    //.. 
    //.. 
} 

Ich habe andere maßgeschneiderte Lösungen gesehen, aber was ich zu einem bestimmten Zeitpunkt in die Finger bekommen habe, beinhaltet immer noch das Sperren eines Threads. Ich will das nicht nur, um die neue "await" -Syntax zu verwenden. Ich suche nach einem wahrlich erwarteten Signalmechanismus, der keinen Thread sperrt.

Ist es etwas, was ich in der Task Parallel Library fehlt?

BEARBEITEN: Nur um klar zu stellen: SomeAsyncAutoResetEvent ist ein vollständig zusammengesetzter Klassenname, der in meinem Beispiel als Platzhalter verwendet wird.

+0

Zum einmaligen Gebrauch, ein 'TaskCompletionSource', deren Ergebnis durch die erwartete Aufgabe ignoriert. –

+0

https://gist.github.com/AArnott/1084951 vielleicht? –

+0

@MatthewWatson Ich sehe, dass es eine Sperre verwendet, die einen Thread aus dem Thread-Pool blockiert. Ich hatte auf etwas gehofft, das keinen blockierten Thread enthielt. –

Antwort

9

Wenn Sie Ihr eigenes bauen möchten, Stephen Toub has the definitive blog post on the subject.

Wenn Sie einen verwenden möchten, der bereits geschrieben wurde, I have one in my AsyncEx library. AFAIK, zum Zeitpunkt des Schreibens gibt es keine anderen Optionen.

+1

Warum funktioniert ein 'neuer SemaphoreSlim (1)' nicht, 'WaitOne()' ist 'WaitAsync()' und 'Set()' wird 'Release()' –

+1

AREs und Semaphore sind sehr ähnlich (obwohl normalerweise anders verwendet)). Der semantische Unterschied kommt zustande, wenn das Primitiv signalisiert wird, wenn es bereits gesetzt ist. –

6

Hier ist die Quelle für Stephen Toub AsyncAutoResetEvent, falls sein Blog offline geht.

public class AsyncAutoResetEvent 
{ 
    private static readonly Task s_completed = Task.FromResult(true); 
    private readonly Queue<TaskCompletionSource<bool>> m_waits = new Queue<TaskCompletionSource<bool>>(); 
    private bool m_signaled; 

    public Task WaitAsync() 
    { 
     lock (m_waits) 
     { 
      if (m_signaled) 
      { 
       m_signaled = false; 
       return s_completed; 
      } 
      else 
      { 
       var tcs = new TaskCompletionSource<bool>(); 
       m_waits.Enqueue(tcs); 
       return tcs.Task; 
      } 
     } 
    } 

    public void Set() 
    { 
     TaskCompletionSource<bool> toRelease = null; 

     lock (m_waits) 
     { 
      if (m_waits.Count > 0) 
       toRelease = m_waits.Dequeue(); 
      else if (!m_signaled) 
       m_signaled = true; 
     } 

     toRelease?.SetResult(true); 
    } 
} 
1

Hier ist eine Version, die ich ausgearbeitet habe, mit der Sie eine Zeitüberschreitung festlegen können. Es ist von Stephen Toubs Lösung abgeleitet. Es wurde grundlegende Tests durchgeführt.

public class AsyncAutoResetEvent 
{ 
    readonly LinkedList<TaskCompletionSource<bool>> waiters = 
     new LinkedList<TaskCompletionSource<bool>>(); 

    bool isSignaled; 

    public AsyncAutoResetEvent(bool signaled) 
    { 
     this.isSignaled = signaled; 
    } 

    public Task<bool> WaitAsync(TimeSpan timeout) 
    { 
     return this.WaitAsync(timeout, CancellationToken.None); 
    } 

    public async Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) 
    { 
     TaskCompletionSource<bool> tcs; 

     lock (this.waiters) 
     { 
      if (this.isSignaled) 
      { 
       this.isSignaled = false; 
       return true; 
      } 
      else if (timeout == TimeSpan.Zero) 
      { 
       return this.isSignaled; 
      } 
      else 
      { 
       tcs = new TaskCompletionSource<bool>(); 
       this.waiters.AddLast(tcs); 
      } 
     } 

     Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)); 
     if (winner == tcs.Task) 
     { 
      // The task was signaled. 
      return true; 
     } 
     else 
     { 
      // We timed-out; remove our reference to the task. 
      // This is an O(n) operation since waiters is a LinkedList<T>. 
      lock (this.waiters) 
      { 
       bool removed = this.waiters.Remove(tcs); 
       Debug.Assert(removed); 
       return false; 
      } 
     } 
    } 

    public void Set() 
    { 
     TaskCompletionSource<bool> toRelease = null; 

     lock (this.waiters) 
     { 
      if (this.waiters.Count > 0) 
      { 
       // Signal the first task in the waiters list. 
       toRelease = this.waiters.First.Value; 
       this.waiters.RemoveFirst(); 
      } 
      else if (!this.isSignaled) 
      { 
       // No tasks are pending 
       this.isSignaled = true; 
      } 
     } 

     if (toRelease != null) 
     { 
      toRelease.SetResult(true); 
     } 
    } 
} 
+1

Ich denke this.waiters sollte in der Remove (tcs) manipulationspfad gesperrt werden? – HelloSam

+0

@HelloSam Ich denke du hast Recht! Fest. Danke, dass du darauf hingewiesen hast. –

1

Ich denke, es ist ein gutes Beispiel auf MSDN: https://msdn.microsoft.com/en-us/library/hh873178%28v=vs.110%29.aspx#WHToTap

public static Task WaitOneAsync(this WaitHandle waitHandle) 
{ 
    if (waitHandle == null) 
     throw new ArgumentNullException("waitHandle"); 

    var tcs = new TaskCompletionSource<bool>(); 
    var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle, 
     delegate { tcs.TrySetResult(true); }, null, -1, true); 
    var t = tcs.Task; 
    t.ContinueWith((antecedent) => rwh.Unregister(null)); 
    return t; 
}