最近在學習.NET4.5關於“並行任務”的使用。“並行任務”有自己的同步機制,沒有顯示給出類似如舊版本的:事件等待句柄、信號量、lock、ReaderWriterLock……等同步基元對象,但我們可以沿溪這一編程習慣,那么這系列翻譯就是給“並行任務”封裝同步基元對象。翻譯資源來源《(譯)關於Async與Await的FAQ》
1. 構建Async同步基元,Part 1 AsyncManualResetEvent
2. 構建Async同步基元,Part 2 AsyncAutoResetEvent
3. 構建Async同步基元,Part 3 AsyncCountdownEvent
4. 構建Async同步基元,Part 4 AsyncBarrier
5. 構建Async同步基元,Part 5 AsyncSemaphore
6. 構建Async同步基元,Part 6 AsyncLock
7. 構建Async同步基元,Part 7 AsyncReaderWriterLock
開始:構建Async同步基元,Part 2 AsyncAutoResetEvent
上一篇,我們構建了async版本的ManualResetEvent。現在,讓我們構建一個async版本的AutoResetEvent。
ManualResetEvent對象,當調用其Set()方法時,ManualResetEvent轉化為有信號狀態,並持有此信號直到顯示調用其Reset()方法重置為無信號狀態,調用Reset()重置為無信號狀態導致下一次WaitOne()等待信號。相比之下,AutoResetEvent也在調用其Set()方法時獲得信號,但是其在調用完WaitOne()后自動重置為無信號狀態。比如,如果ManualResetEvent上有4個等待線程,當其中一個線程調用set()方法后,則4個等待線程都將完成。相比之下,如果AutoResetEvent上有4個等待線程,當其中一個線程調用set()方法后,僅僅是其中一個線程獲得信號,其他3個WaitHandle依然為無信號狀態(需要注意的是AutoResetEvent常常會導致無法跟蹤信號的到達。比如:起初沒有線程等待,事件接收了兩次set()信號,然后兩個線程再在事件上等待信號,這時只有其中一個完成等待---可以參考示例m_signaled變量的實現)。
這是我們將構建的目標類型:
public class AsyncAutoResetEvent { public Task WaitAsync(); public void Set(); }
首先,我們需要一些成員(eg:TaskCompletionSource<TResult>)。上一篇中的AsyncManualResetEvent,我們只需要單個TaskCompletionSource<TResult>實例,因為事件的通知會喚醒當前所有等待。但是AsyncAutoResetEvent不一樣,我們需要區別對待不同的等待,因為如果有多個線程等待,一個信號到達只能喚醒一個等待。所以,我們需要一個TaskCompletionSource<TResult>實例集合。此外,信號到達時可能沒有等待者,所以我們需要使用一個bool變量進行跟蹤。最后,在一些情況下我們能重用已完成的Task來提高性能,所以我將一直保持其中一個引用,設計如下:
private readonly static Task s_completed = Task.FromResult(true); private readonly Queue<TaskCompletionSource<bool> m_waits = new Queue<TaskCompletionSource<bool>(); private bool m_signaled;
接下來,讓我們實現一個命名為WaitAsync()的方法。當調用WaitAsync()時,如果m_signaled為true,我們直接返回已經完成的s_completed,因為這個等待調用消費這個信號,所以我們需要將m_signaled重置為false。如果m_signaled為false,我們將創建一個TaskCompletionSource<TResult>實例插入m_waits隊列中,並且返回實例對應的Task。這個Task將推遲完成直到有線程調用Task的Set()方法並且喚醒等待者。但是要注意,這里需要保持多個操作執行的原子性,因此,我對m_waits隊列加鎖以確保同步。
public Task WaitAsync() { lock (m_waits) { if (m_signaled) { m_signaled = false; returns_completed; } else { var tcs = new TaskCompletionSource<bool>(); m_waits.Enqueue(tcs); return tcs.Task; } } }
接下來,我將實現Set()方法。Set()方法將首先檢查m_waits隊列中是否有等待者。如果有,則從隊列中取出一個TaskCompletionSource<bool>並且完成它。如果m_waits隊列為空,則只是簡單的將m_signaled設置為true。這里的操作需要保持原子性,並且要與WaitAsync()方法保持同步,所以set()的主體代碼需要再一次對m_waits隊列加鎖。這里要注意一個重要的事情,在之前的文章中,我討論了TaskCompletionSource<TResult>的 [Try]Set*() 系列方法,會使TaskCompletionSource<TResult>對應的Task作為同步調用的一部分運行。如果我們在lock內部調用SetResilt(),則Task的同步延續的運行將長時間持有lock。因此,我們釋放lock后再調用Task的[Try]Set*() 系列方法來完成任務。
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; } if (toRelease != null) toRelease.SetResult(true); }
這就是本節要講的AsyncAutoResetEvent。
完整源碼如下:
public class AsyncAutoResetEvent { // 保存一個成功完成的 Task<TResult>,供重用以提高性能 private readonly static Task s_completed = Task.FromResult(true); // 等待任務隊列 private readonly Queue<TaskCompletionSource<bool>> m_waits = new Queue<TaskCompletionSource<bool>>(); // 用於跟蹤 信號到達時可能沒有等待者 的情況,將AsyncAutoResetEvent的初始狀態設置為有信號 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; } if (toRelease != null) toRelease.SetResult(true); } }
下一節,我將實現一個async版本的CountdownEvent。
推薦閱讀:
感謝你的觀看……
原文:《Building Async Coordination Primitives, Part 2: AsyncAutoResetEvent》