(一)異步編程的重要性
使用異步編程,方法調用是在後台運行(通常在線程或任務的幫助下),並不會阻塞調用線程。有3中不同的異步編程模式:異步模式、基於事件的異步模式和新增加的基於任務的異步模式(TAP,可利用async和await關鍵字來實現)。
(二)異步模式
1、C#1的APM 異步編程模型(Asynchronous Programming Model)。
2、C#2的EAP 基於事件的異步模式(Event-based Asynchronous Pattern)。
3、TAP 基於任務的異步模式(Task-based Asynchronous Pattern)。
參考:http://www.cnblogs.com/zhaopei/p/async_one.html
(三)異步編程基礎
async和await關鍵字只是編譯器功能。編譯器會用Task類創建代碼。
1、創建任務
1 /// <summary>
2 /// 同步方法
3 /// </summary>
4 /// <returns></returns>
5 static string SayHi(string name)
6 {
7 Thread.Sleep(3000);
8 return "你好!"+ name;
9 }
10
11 /// <summary>
12 /// 基於任務的異步模式
13 /// </summary>
14 /// <returns></returns>
15 static Task<string> SayHiAsync(string name)
16 {
17 return Task.Run<string>(()=> {
18 return SayHi(name);
19 });
20 }
泛型版本的Task.Run<string>創建一個返回字符串的任務。
2、調用異步方法
使用await關鍵字需要有用async修飾符聲明的方法。在await的方法沒有完成前,該方法內的其他代碼不會繼續執行,但是調用await所在方法的線程不會被阻塞。
private async static void CallerWithAsync()
{
string result = await SayHiAsync("張三");
Console.WriteLine(result);
}
async修飾符只能用於返回Task和void方法。
3、延續任務
Task類的ContinueWith方法定義了任務完成後就調用的代碼。指派給ContinueWith方法的委托接受將已完成的任務作為參數傳入,使用Result屬性可以訪問任務返回的結果。
private static void CallerWithContinuationTask()
{
Task<string> t = SayHiAsync("李四");
t.ContinueWith(_t => Console.WriteLine(_t.Result));
}
4、同步上下文
WPF應用程序設置了DispatcherSynchronizationContext屬性,WindowsForm應用程序設置了WindowsFormsSynchronizationContext屬性。如果調用異步方法的線程分配給了同步上下文,await完成之後將繼續執行。如果不使用相同的上下文,必須調用Task類的ConfigureAwait(ContinueOnCapturedContext:false)。
5、使用多個異步方法
(1)按順序調用異步方法
private async static void MultipleCallerWithAsync()
{
string result1 = await SayHiAsync("張三");
string result2 = await SayHiAsync("李四");
Console.WriteLine("完成了兩次打招呼!{0} 和 {1}", result1, result2);
}
(2)使用組合器
一個組合器可以接受多個同一類型的參數,並返回同一類型的值。Task組合器接受多個Task對象作為參數,並返回一個Task。
private async static void MultipleCallerWithAsyncWithCombinators1()
{
Task<string> task1 = SayHiAsync("張三");
Task<string> task2 = SayHiAsync("李四");
await Task.WhenAll(task1,task2);
Console.WriteLine("完成了兩次打招呼!{0} 和 {1}", result1, result2);
}
Task類定義了WhenAll(全部任務完成才返回)和WhenAny(任意任務完成即返回)兩個組合器。
當任務返回類型相同時,可以用數組接受返回結果。
private async static void MultipleCallerWithAsyncWithCombinators2()
{
Task<string> task1 = SayHiAsync("張三");
Task<string> task2 = SayHiAsync("李四");
string [] results=await Task.WhenAll(task1,task2);
Console.WriteLine("完成了兩次打招呼!{0} 和 {1}", results[0], results[1]);
}
6、轉換異步模式
當某些類沒有提供基於任務的異步模式時(僅有BeginXX,EndXX),可以使用TaskFactory類定義的FromAsync方法轉換為基於任務的異步模式的方法。
private static async void ConvertingAsyncPattern()
{
Func<string, string> method = SayHi;
string result = await Task<string>.Factory.FromAsync<string>((name, callback,state) => {
return method.BeginInvoke(name, callback, state);
},ar=> {
return method.EndInvoke(ar);
},"王麻子",null);
Console.WriteLine(result);
}
(四)錯誤處理
1、異步方法的異常處理
異步方法異常的一個較好的處理方式,就是使用await關鍵字,將其放在try/catch語句中。
1 static async Task ThrowAfter(int ms, string message)
2 {
3 await Task.Delay(ms);
4 throw new Exception(message);
5 }
6
7 private static async void HandleOneError()
8 {
9 try
10 {
11 await ThrowAfter(2000, "first");
12 }
13 catch (Exception ex)
14 {
15 Console.WriteLine("handled {0}", ex.Message);
16 }
17 }
2、多個異步方法的異常處理
在順序調用兩個及以上會拋出異常的方法時,不可再使用以上方法,因為當第一個異步方法拋出異常時try塊裡的余下方法不會再被調用。
如果需要將剩余的方法繼續執行完,再對異常進行處理,可以使用Task.WhenAll方法,這樣不管任務是否拋出異常,都會等到所有任務執行完。但是,也只能看見傳遞給WhenAll方法的第一個異常。
private static async void HandleOneError()
{
try
{
Task task1 = ThrowAfter(1000, "first");
Task task2 = ThrowAfter(2000, "second");
await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
Console.WriteLine("handled {0}", ex.Message);
}
}
如果需要將剩余的方法執行完,且獲取所有拋出異常,可以在try塊外聲明任務變量,使其可以在catch塊內被訪問。這樣可以使用IsFaulted屬性檢查任務的狀態,當為true時,可以使用Task類的Exception.InnerException訪問異常信息。
private static async void HandleOneError()
{
Task task1 = null;
Task task2 = null;
try
{
task1 = ThrowAfter(1000, "first");
task2 = ThrowAfter(2000, "second");
await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
if (task1 != null && task1.IsFaulted)
{
Console.WriteLine(task1.Exception.InnerException);
}
if (task2 != null && task2.IsFaulted)
{
Console.WriteLine(task2.Exception.InnerException);
}
}
}
3、使用AggregateException信息
為了得到所有的異常信息,還可以將Task.WhenAll返回的結果寫入一個Task變量中,然後訪問Task類的Exception屬性(AggregateException類型)。AggregateException定義了InnerExceptions屬性,它包含了所有的異常信息。
private static async void HandleOneError()
{
Task task = null;
try
{
Task task1 = ThrowAfter(1000, "first");
Task task2 = ThrowAfter(2000, "second");
await (task = Task.WhenAll(task1, task2));
}
catch (Exception)
{
foreach (var exception in task.Exception.InnerExceptions)
{
Console.WriteLine(exception);
}
}
}
(五)取消
1、取消任務
private CancellationTokenSource cts;
private void OnCancel()
{
if (cts != null)
{
cts.Cancel();
//cts.CancelAfter(1000);//等待1000ms後取消
}
}
2、使用框架特性取消任務
private async void OnTaskBasedAsyncPattern()
{
List<string> urlList = new List<string>();
urlList.Add("http://www.baidu.com");
cts = new CancellationTokenSource();
try
{
foreach (var url in urlList)
{
Random rd = new Random();
int i = rd.Next(1, 100); //1到100之間的數,
if (i%2==0)
{
OnCancel();//當隨機數為偶數時取消任務
}
var client = new HttpClient();
var response = await client.GetAsync(url, cts.Token);//GetAsync方法會檢查是否應該取消操作
var result =await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
}
catch (OperationCanceledException ex)//當任務取消時會拋出該異常
{
Console.WriteLine(ex.Message);
}
}
3、取消自定義任務
Task類的Run方法提供了傳遞CancellationToken參數的重載版本。使用IsCancellationRequest屬性檢查令牌,用ThrowIfCancellationRequested方法觸發異常。
public async void CustomerTask()
{
cts = new CancellationTokenSource();
var list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
var deal_list = new List<int>();
try
{
await Task.Run(() => {
foreach (var item in list)
{
Random rd = new Random();
int i = rd.Next(1, 100); //1到100之間的數,
if (i % 2 == 0)
{
OnCancel();//當隨機數為偶數時取消任務
}
if (cts.Token.IsCancellationRequested)
{
Console.WriteLine("處理任務異常,回滾");
deal_list.Clear();
cts.Token.ThrowIfCancellationRequested();
}
deal_list.Add(Convert.ToInt32(item));
Console.WriteLine(item);
}
}, cts.Token);
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
}