與其他.Net異步模式和類型進行互操作


返回該系列目錄《基於Task的異步模式--全面介紹》


Tasks和異步編程模型APM(Tasks and the Asynchronous Programming Model)

從APM到Tasks

APM模式依賴兩個對應的方法來表示一個異步操作:BeginMethodName和EndMethodName。在高級別,begin方法接受的參數和相應的同步方法MethodName的參數是一樣的,而且還接受一個AsyncCallback和一個object state。begin方法然后返回IAsyncResult,IAsyncResult從它的AsyncState屬性返回傳遞給begin方法的object state。異步操作完成時,IAsyncResult的IsCompleted屬性會開始返回true,且會設置它的AsyncWaitHandle屬性。而且,如果begin方法的AsyncCallback參數是非空的,那么會調用callback,且將它傳給從begin方法返回的相同的IAsyncResult。當異步操作確實完成時,會使用EndMethodName方法連接該操作,檢索任何結果或者強制產生的異常傳播。

由於APM模式結構的本質,構建一個APM的包裝器來將它暴露為一個TAP實現是相當容易的。實際上,.Net Framework 4 以TaskFactory.FromAsync的形式提供了轉化的幫助路線。

思考.Net 中的Stream類和BeginRead/EndRead 方法,它們都代表了同步的Read方法的APM對應版本:

public int Read(
    byte [] buffer, int offset, int count);
…
public IAsyncResult BeginRead(
    byte [] buffer, int offset, int count, 
    AsyncCallback callback, object state);
public int EndRead(IAsyncResult asyncResult);

利用FromAsycn,可實現該方法的TAP包裝器:

public static Task<int> ReadAsync(
    this Stream stream, byte [] buffer, int offset, int count)
{
    if (stream == null) throw new ArgumentNullException(“stream”);
    return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead,
        buffer, offset, count, null);
}

這個使用了FromAsync的實現和下面的具有同樣效果:

public static Task<int> ReadAsync(
    this Stream stream, byte [] buffer, int offset, int count)
{
    if (stream == null) throw new ArgumentNullException(“stream”);
    var tcs = new TaskCompletionSource<int>();
    stream.BeginRead(buffer, offset, count, iar =>
    {
        try { tcs.TrySetResult(stream.EndRead(iar)); }
        catch(OperationCanceledException) { tcs.TrySetCanceled(); }
        catch(Exception exc) { tcs.TrySetException(exc); }
    }, null);
    return tcs.Task;
}

從Tasks到APM

對於現有的基礎設施期望代碼實現APM模式的場合,能夠采取TAP實現以及在期待TAP實現的地方使用它也是很重要的。幸好有了tasks的組合性,以及Task本身實現IAsyncResult的事實,使用一個簡單的幫助函數就可以實現了(這里展示的是一個Task<TResult>的擴展,但幾乎相同的函數可能用於非泛型的Task):

 

public static IAsyncResult AsApm<T>(
    this Task<T> task, AsyncCallback callback, object state)
{
    if (task == null) throw new ArgumentNullException(“task”);
    var tcs = new TaskCompletionSource<T>(state);
    task.ContinueWith(t =>
    {
        if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions)
        else if (t.IsCanceled) tcs.TrySetCanceled();
        else tcs.TrySetResult(t.Result);

        if (callback != null) callback(tcs.Task);
    }, TaskScheduler.Default);
    return tcs.Task;
}

現在,想一個有TAP實現的場合:

public static Task<string> DownloadStringAsync(Uri url);

且我們需要提供APM實現:

public IAsyncResult BeginDownloadString(
    Uri url, AsyncCallback callback, object state);
public string EndDownloadString(IAsyncResult asyncResult);

可以通過下面代碼實現:

public IAsyncResult BeginDownloadString(
    Uri url, AsyncCallback callback, object state)
{
    return DownloadStringAsync(url).AsApm(callback, state);
}

public string EndDownloadString(IAsyncResult asyncResult)
{
    return ((Task<string>)asyncResult).Result;
}

 

Tasks和基於事件的異步模式EAP(Event-based Asynchronous Pattern)

基於事件的異步模式依賴於一個返回void的實例MethodNameAsync方法,接收和同步方法MethodName方法相同的參數,並且要實例化異步操作。實例異步操作之前,事件句柄使用相同實例上的事件注冊,然后觸發這些事件來提供進度和完成通知。事件句柄一般都是自定義的委托類型,該委托類型利用了派生自ProgressChangedEventArgs或AsyncCompletedEventArgs的事件參數類型。

包裝一個EAP實現更復雜一些,因為該模式本身牽扯了比APM模式更多的變量和更少的結構。為了演示,接下來包裝一個DownloadStringAsync方法。DownloadStringAsync接受一個Uri參數,為了上報多個進度上的統計數據,下載時會觸發DownloadProgressChanged 事件,完成時會觸發DownloadStringCompleted 事件。最終結果是一個包含在指定Uri的頁面內容的字符串。

public static Task<string> DownloadStringAsync(Uri url)
{
    var tcs = new TaskCompletionSource<string>();
    var wc = new WebClient();
    wc.DownloadStringCompleted += (s,e) =>
    {
        if (e.Error != null) tcs.TrySetException(e.Error);
        else if (e.Cancelled) tcs.TrySetCanceled();
        else tcs.TrySetResult(e.Result);
    };
    wc.DownloadStringAsync(url);
    return tcs.Task;
}

Tasks和等待句柄(WaitHandlers)

從WaitHandlers到Tasks

高級的開發人員可能會發現,WaitHandle 設置時,自己利用 WaitHandles 和線程池的 RegisterWaitForSingleObject 方法進行異步通知,然而這本質上不是一個異步模式 。我們可以包裝RegisterWaitForSingleObject來啟用WaitHandle之上的任何異步等待的基於task的選擇:

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(_ => rwh.Unregister(null));
    return t;
}

使用那些之前演示的構建於Task之上的數據結構的技巧,相似地,構建一個不依賴WaitHandles且完全以Task的角度工作的異步信號燈(semaphore)也是可能的。事實上,.Net 4.5中的SemaphoreSlim 類型暴露了開啟這個的WaitAsync方法。

比如,之前提到的System.Threading.Tasks.Dataflow.dll中的BufferBlock<T>類型可以這樣使用:

static SemaphoreSlim m_throttle = new SemaphoreSlim(N, N);

static async Task DoOperation()
{
    await m_throttle.WaitAsync();
    … // do work
    m_throttle.Release ();
}

從Tasks到WaitHandlers

如之前提到的,Task類實現了IAsyncResult,該IAsyncResult的實現暴露了一個返回WaitHandle的AsycnWaitHandle屬性,此WaitHandle是在Task完成時設置的。照這樣,獲得一個Task的WaitHandle可以像下面這樣實現:

WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;

 

返回該系列目錄《基於Task的異步模式--全面介紹》



免責聲明!

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



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