2015-09-18 5 views
11

Jaki byłby asynchroniczny (oczekiwany) odpowiednik AutoResetEvent?Awaitable AutoResetEvent

Jeśli w klasycznej synchronizacji wątku będziemy używać coś takiego:

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 
     //.. 
     //.. 
    } 

Miałem nadzieję, że w nowym asynchronicznym sposób robienia rzeczy, coś takiego przyjdzie się:

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 
    //.. 
    //.. 
} 

Widziałem inne niestandardowe rozwiązania, ale to, co udało mi się zdobyć, w pewnym momencie, wciąż wymaga zablokowania wątku. Nie chcę tego tylko ze względu na użycie nowej, oczekującej składni. Szukam prawdziwego wyczekiwanego mechanizmu sygnalizacji, który nie blokuje żadnego wątku.

Czy jest coś, czego mi brakuje w bibliotece zadań równoległych?

EDYCJA: Aby wyjaśnić: SomeAsyncAutoResetEvent jest całkowicie sklasyfikowaną nazwą klasy używaną jako symbol zastępczy w moim przykładzie.

+0

Dla jednorazowy użytkowania, 'TaskCompletionSource' którego wynikiem jest ignorowany przez zadanie czeka. –

+0

https://gist.github.com/AArnott/1084951 może? –

+0

@MatthewWatson Widzę, że używa blokady, która zablokuje wątek z puli wątków. Miałem nadzieję na coś niezwiązanego z zablokowaną nicią. –

Odpowiedz

9

Jeśli chcesz zbudować własny, Stephen Toub has the definitive blog post on the subject.

Jeśli chcesz użyć takiego, który został już napisany, I have one in my AsyncEx library. AFAIK, nie ma innych opcji od czasu tego pisania.

+1

Dlaczego 'nowy semaforaSlim (1)' działa, 'WaitOne()' to 'WaitAsync()' i 'Set()' staje się 'Release()' –

+1

AREs i Semaphores są bardzo podobne (choć zwykle używane inaczej). Różnica semantyczna pojawia się, jeśli prymityw jest sygnalizowany, gdy jest już ustawiony. –

6

Oto źródło Stephen Toub's AsyncAutoResetEvent, na wypadek, gdyby jego blog został wyłączony.

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

Oto przygotowana wersja, która pozwala określić limit czasu. Pochodzi od rozwiązania Stephena Touba. Przeprowadzono podstawowe testy scenariuszy.

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

Myślę, że this.Wayiters powinny być zablokowane na ścieżce manipulacji Remove (tcs)? – HelloSam

+0

@HelloSam Myślę, że masz rację! Naprawiony. Dzięki za wskazanie tego. –

1

myślę, że jest dobrym przykładem na 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; 
} 
Powiązane problemy