程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 【讀書筆記】C#高級編程 第十三章 異步編程,

【讀書筆記】C#高級編程 第十三章 異步編程,

編輯:C#入門知識

【讀書筆記】C#高級編程 第十三章 異步編程,


(一)異步編程的重要性

使用異步編程,方法調用是在後台運行(通常在線程或任務的幫助下),並不會阻塞調用線程。有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);
    }
   
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved