(譯)構建Async同步基元,Part 2 AsyncAutoResetEvent


傳送門:異步編程系列目錄……

最近在學習.NET4.5關於“並行任務”的使用。“並行任務”有自己的同步機制,沒有顯示給出類似如舊版本的:事件等待句柄、信號量、lockReaderWriterLock……等同步基元對象,但我們可以沿溪這一編程習慣,那么這系列翻譯就是給“並行任務”封裝同步基元對象。翻譯資源來源《(譯)關於AsyncAwaitFAQ

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同步基元.rar

開始:構建Async同步基元,Part 2 AsyncAutoResetEvent

         上一篇,我們構建了async版本的ManualResetEvent。現在,讓我們構建一個async版本的AutoResetEvent

         ManualResetEvent對象,當調用其Set()方法時,ManualResetEvent轉化為有信號狀態,並持有此信號直到顯示調用其Reset()方法重置為無信號狀態,調用Reset()重置為無信號狀態導致下一次WaitOne()等待信號。相比之下,AutoResetEvent也在調用其Set()方法時獲得信號,但是其在調用完WaitOne()后自動重置為無信號狀態。比如,如果ManualResetEvent上有4個等待線程,當其中一個線程調用set()方法后,則4個等待線程都將完成。相比之下,如果AutoResetEvent上有4個等待線程,當其中一個線程調用set()方法后,僅僅是其中一個線程獲得信號,其他3WaitHandle依然為無信號狀態(需要注意的是AutoResetEvent常常會導致無法跟蹤信號的到達。比如:起初沒有線程等待,事件接收了兩次set()信號,然后兩個線程再在事件上等待信號,這時只有其中一個完成等待---可以參考示例m_signaled變量的實現)

         這是我們將構建的目標類型:

public class AsyncAutoResetEvent
{ 
    public Task WaitAsync(); 
    public void Set(); 
}

首先,我們需要一些成員(egTaskCompletionSource<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_signaledtrue,我們直接返回已經完成的s_completed,因為這個等待調用消費這個信號,所以我們需要將m_signaled重置為false。如果m_signaledfalse,我們將創建一個TaskCompletionSource<TResult>實例插入m_waits隊列中,並且返回實例對應的Task。這個Task將推遲完成直到有線程調用TaskSet()方法並且喚醒等待者。但是要注意,這里需要保持多個操作執行的原子性,因此,我對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

作者:Stephen Toub – MSFT

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM