EAP 事件架構非同步模式 Event-based Asynchronous Pattern 對應到 TAP 以工作為基礎的非同步模式
當我們進行設計非同步應用程式碼的時候,除了要能夠讓程式碼進行非同步的多工處理,也需要針對非同步工作執行中,若發生了例外異常的時候,不會造成應用程式的崩壞,也需要能夠提供取消非同步方法的需求。在這篇文章中,將會針對使用 EAP 事件架構非同步模式 的 WebClient 這個類別,把這個類別提供的取消、發生例外異常的機制,轉換到 Task 物件之內。
想要設計一個非同步的工作,最為簡單的方式,那就是直接使用
Task.Run
方法,並且指定一個委派方法,如此,就會得到一個 Task 物件,開發者就可以使用 await 來等候這個非同步工作完成,不過,當這個非同步工作在執行中有例外異常發生或者有取消需求產生的時候,使用 await 等候的非同步工作的時候,會拋出一個例外異常出來,為了要讓整個應用程式不會崩潰,因此,需要使用 try...catch 敘述把 await 敘述包起來,針對所發生的非同步異常事件,分別進行處理。
首先,需要先把 WebClient 這個類別的非同步功能,打包成為一個 Task 工作為基礎的非同步工作,這裡,使用的是 TaskCompletionSource 這個類別,雖然這個非同步工作沒有任何回傳值,不過,當使用這個類別的時候,還是要指定一個泛型型別,在這個將會使用 object 型別,當這個非同步工作完成的時候,可以使用 tcs.SetResult(null); 設定這個非同步工作已經順利完成。
但是,當這個事件架構非同步應用發生了例外異常, WebClient 的 回呼 callback 委派方法內,將可以透過參數 DownloadStringCompletedEventArgs.Error 得到這次發生了甚麼例外異常,接下來就可以使用 TaskCompletionSource.SetException 方法,把 DownloadStringCompletedEventArgs.Error 屬性值傳遞到這個方法之內,如此,當使用 await 關鍵字 來等候這個非同步工作的時候,也就可會同樣的接收到這個例外異常。
對於取消功能,可以透過 WebClient.CancelAsync() 方法送出取消通知,在這個範例中,將會使用一個 Task.Run 來做到這件事情,這樣將會在另外一個執行緒下,先等候 2 秒鐘,接著,執行 WebClient.CancelAsync() 方法,取消這個非同步網路存取需求。如此,在 WebClient 的 callback 委派方法內,便可以透過 接著,在使用 DownloadStringCompletedEventArgs.Cancelled 屬性得知這次的 WebClient 存取過程中,是否有發生了取消請求,若有的畫,可以使用 TaskCompletionSource.SetCanceled() 方法,設定這個非同步工作屬於取消狀態。此時,當使用 await 等候這個非同步工作的時候,就會得到一個 TaskCanceledException 例外異常,如此,可以針對取消非同步工作事件發生之後,進行相關的程式碼狀態清除的作業。
private static Task EAPtoTask(string url, bool needCancel)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
WebClient client = new WebClient();
if (needCancel)
{
Task.Run(() =>
{
Console.WriteLine($"網路存取將會於2秒後取消");
Thread.Sleep(2000);
Console.WriteLine($"對 WebClient 送出取消");
client.CancelAsync();
});
}
client.Encoding = Encoding.UTF8;
client.DownloadStringCompleted += (s, e) =>
{
if (e.Cancelled)
{
tcs.SetCanceled();
}
else if (e.Error != null)
{
Console.WriteLine($"喔喔,EAP 內的 callback 中得到有例外異常發生 {e.Error.Message}...");
tcs.SetException(e.Error);
}
else
{
Console.WriteLine($"{e.Result}");
tcs.SetResult(null);
}
};
client.DownloadStringAsync(new Uri(url));
return tcs.Task;
}
現在,來進行測試,首先,是要測試取消需求,當要存取這個 URL ( https://lobworkshop.azurewebsites.net/api/RemoteSource/Source1 )的時候,該 URL 將會超過 3 秒種以上的時間,才會回傳結果,不過,此時將會在啟動 WebClient 非同步需求後的 2 秒鐘後,就會對 WebClient 送出取消請求。現在,將會得到底下的執行結果:
網路存取將會於2秒後取消
對 WebClient 送出取消
非同步工作發現到有取消 A task was canceled.
Press any key for continuing...
try
{
await EAPtoTask("https://lobworkshop.azurewebsites.net/api/RemoteSource/Source1", true);
}
catch (TaskCanceledException exCancellation)
{
Console.WriteLine($"非同步工作發現到有取消 {exCancellation.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"非同步工作發現到有例外異常 {ex.Message}");
}
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
接著要來呼叫 https://lobworkshop.azurewebsites.net/api/RemoteSource/SampleX Web API 服務,不過,這個 URL 並不存在,因此,當使用 WebClient 啟動非同步呼叫的時候,將會得到一個 WebException 例外異常,而執行結果如下:
喔喔,EAP 內的 callback 中得到有例外異常發生 The remote server returned an error: (404) Not Found....
非同步工作發現到有例外異常 The remote server returned an error: (404) Not Found.
Press any key for continuing...
try
{
await EAPtoTask("https://lobworkshop.azurewebsites.net/api/RemoteSource/SampleX", false);
}
catch (TaskCanceledException exCancellation)
{
Console.WriteLine($"非同步工作發現到有取消 {exCancellation.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"非同步工作發現到有例外異常 {ex.Message}");
}
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
當要呼叫 https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample 服務的時候,就可以正常取得遠端伺服器的回應文字內容,執行結果如下:
來自遠端 ASP.NET Core Web API 服務的資料
Press any key for continuing...
try
{
await EAPtoTask("https://lobworkshop.azurewebsites.net/api/RemoteSource/Sample", false);
}
catch (TaskCanceledException exCancellation)
{
Console.WriteLine($"非同步工作發現到有取消 {exCancellation.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"非同步工作發現到有例外異常 {ex.Message}");
}
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();