與其餘.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方法鏈接該操做,檢索任何結果或者強制產生的異常傳播。html

因爲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的實現和下面的具備一樣效果:async

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實現:this

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

能夠經過下面代碼實現:url

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的事件參數類型。spa

包裝一個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的異步模式--全面介紹》

相關文章
相關標籤/搜索