.NET異步程序設計之async&await

shanzm-2020年3月7日 23:12:53

0.背景引入

如今的.net異步編程中,通常都是使用 基於任務異步模式(Task-based Asynchronous Pattern,TAP)實現異步編程git

可參考微軟文檔:使用基於任務的異步模式github

基於任務的異步模式,是使用System.Threading.Tasks.Task 命名空間中的System.Threading.Tasks.Task<TResult>System.Threading.Tasks類型實現異步編程,編程

配合着C#5.0(.net 4.5)中添加的兩個關於異步編程的關鍵字asyncawait,能夠快速的實現異步操做。數組




1.async和await基本語法

1.1 簡介

在C#5.0(.net 4.5)中添加了兩個關於異步編程的關鍵字asyncawait服務器

兩個關鍵字能夠快速的建立和使用異步方法。asyncawait關鍵字只是編譯器功能,編譯後會用Task類建立代碼,實現異步編程。其實只是C#對異步編程的語法糖,本質上是對Task對象的包裝操做。多線程

因此,若是不使用這兩個關鍵字,也能夠用C#4.0中Task類的方法來實現一樣的功能,只是沒有那麼方便。(見:.NET異步程序設計之任務並行庫)mvc

1.2 具體使用方法

  1. async:用於異步方法的函數名前作修飾符,處於返回類型左側。async只是一個標識符,只是說明該方法中有一個或多個await表達式,async自己並不建立異步操做。異步

  2. await:用於異步方法內部,用於指明須要異步執行的操做(稱之爲await表達式),注意一個異步方法中能夠有多個await表達式,並且應該至少有一個(如果沒有的話編譯的時候會警告,但仍是能夠構建和執行,只不過就至關於同步方法而已)。async

    其實這裏你有沒有想起Task類中爲了實現延續任務而使用的等待者,好比:使用task.GetAwaiter()方法爲task類建立一個等待者(能夠參考;3.3.2使用Awaiter)。await關鍵字就是基於 .net 4.5中的awaiter對象實現的。

  3. 總而言之,如果有await表達式則函數必定要用async修飾,如果使用了async修飾的方法中沒有await表達式,編譯的時候會警告!

1.3 返回值類型

  1. 使用asyncawait定義的異步方法的返回值只有三種:Task<T>Taskvoid

  2. 異步方法中有return語句,則返回值類型爲Task<T>,其中T是return語句返回對象的類型。(編譯器幫咱們把T類型數據轉換爲Task<T>類型)。因此不要使用return返回一個Task<T>類型的對象,而是隻須要返回一個T類型數據便可

  3. 異步方法中沒有return語句,可是你須要查看異步方法的狀態或是要對Task對象操做(好比task.Wait()),則可定義返回值類型爲Task

  4. 異步方法中沒有return語句且不須要查看異步方法的狀態,則可認爲是返回值是void類型。此時稱之爲「調用並忘記」(fire and forget),其實這並非一個很好的用法(具體的分析能夠看:異步方法的異常處理)!

  5. 如果真的須要返回一個Task<T>類型的數據 (好比在非async修飾的方法中,定義返回值類型就是一個Task<T>類型),則:

    • return Task.Run(()=>{……})

    • T類型轉換爲Task<T>類型:return Task.FromResult(T data)

    • IAsyncResult類型對象轉換爲Task類型:return Task.Factory.FromTask(IAsyncResult data)

1.4 其餘細節

  1. async修飾符只能用於返回TaskTask<T>viod的方法,或者Lambda表達式中。

  2. async不能用於程序的入口點,即Main()不能使用async修飾符。 謝謝@coredx提醒:C#7.1中應用程序的入口點能夠具備async修飾符,參考:What's new in C# 7.1

1.5 async傳染性

  1. 簡單的說:函數體中含有await表達式的函數,必須使用async修飾!

    而一個使用了async修飾的方法,在調用它的時候若有必要則必須使用await等待!

    使用了await等待的方法,則其調用方法又必須使用async修飾,這從而造成了一個循環,這就是async傳染

    換句話說就是哪裏調用了async修飾的方法則async就會傳染到哪!

    能夠有的時候咱們並不想要咱們的方法變爲async修飾的方法,因此須要避免async傳染

    避免的主要方法就是使用延續任務來避免,你想想以前在Task類中,使用延續任務時,主要就是避免使用await等待!

    參考:C# 5.0中同步執行異步方法

  2. 以前咱們說了:主函數Main()不能使用async修飾符,由於Main函數不可以是異步方法,這也就意味着一切的異步方法最終的調用方法必定是同步方法(C#7.1Main能夠是異步的),而調用異步方法的那個同步方法,稱之爲病源隔斷方法,由於在這裏開始,再也不會發生async傳染。

1.6 簡單示例

示例1:定義一個異步方法,並調用它

static void Main(string[] args)
{   
    Task<int> t = SumAsync(1, 2);
    t.ContinueWith(t => Console.WriteLine( "異步操做結果爲:" + t.Result));
    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"循環次數{i}");
    }
    Console.ReadKey();
}

private static async Task<int> SumAsync(int num1, int num2)
{
    int sum = await Task.Run(() => { Thread.Sleep(3000); return num1 + num2; });
    return sum;
}

示例說明

  1. 異步方法SumAsync()函數體中返回的是整型sum,即返回的是一個整型,可是在聲明函數時,返回值需寫爲:Task<int>

    反過來講:如果異步方法的返回值類型爲Task<T>,則在方法中只須要返回T類型的數據

    這一點就是和Task.Run()的返回值的書寫方式同樣,即若Task.Run()參數是有返回值的委託Func<TResult>,則Task.Run()返回值是Task<TResult>泛型

  2. 異步方法的命名默認是在最後加"Async",即"XXXAsync"。

  3. 調用異步方法的方法稱之爲調用方法(在這裏就是主函數Main()),調用方法和被調用的異步方法,不必定在不一樣的線程中

  4. 其實你看上面示例的代碼也會發現,單獨把異步操做封裝爲一個異步方法,這樣能夠爲異步操做傳參!
    你能夠還記得的在.net異步程序設計之任務並行庫中,屢次說明Task.Run()的參數只能是無參委託。

  5. 有一點在這裏說明一下:關於異步匿名方法,或是異步Lambda表達式。
    在爲一個事件綁定事件處理程序的時候,對於一些簡單的事件處理程序,咱們可使用Lambda表達式
    可是咱們想要異步操做Lambda表達式,則能夠直接寫爲:

Butten.Click+=async(sender,e)=>{...await...}

詳細能夠參照《C#圖解教程:20.5使用異步Lambda表達式》




2.異步方法的執行順序

依舊是上面的示例,咱們在每一個操做中、操做前、操做後都打印其當前所處的線程,仔細的觀察,異步方法的執行順序。

再次強調,這裏用async修飾的方法,稱之爲異步方法,這裏調用該異步方法的方法,稱之爲調用方法

代碼:

//調用方法
static void Main(string[] args)
{
    Console.WriteLine($"-1-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------調用方法中調用異步方法以前的代碼");
    Task<int> result = SumAsync(1, 2);
    Console.WriteLine($"-3-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------調用方法中調用異步方法以後的代碼");
    result.ContinueWith(t => Console.WriteLine($"-8-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------這是延續任務的線程" + "-異步操做結果爲:" + result.Result));
    Console.WriteLine($"-4-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------調用方法中延續任務以後的代碼");
    for (int i = 0; i < 5; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}"+$"循環次數{i}--------------------------------------調用方法中延續任務以後的代碼");
    }
    Console.ReadKey();
}

//異步方法
private static async Task<int> SumAsync(int num1, int num2)
{
    Console.WriteLine($"-2-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------異步方法中await表達式以前的代碼");
    int sum1 = await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"-5-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------這是第一個await表達式的線程"); return num1 + num2; });
    Console.WriteLine($"-6-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------異步方法中await表達式以後的代碼");
    int sum2=await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"-7-.正在執行的線程,線程ID:{Thread.CurrentThread.ManagedThreadId,2}------------------這是第二個await表達式的線程"); return num1 + num2; });
    return sum1+sum2;
}

運行結果:

說明

注意運行順序:

調用異步方法方法後,按照同步順序執行異步方法中await表達式以前的代碼,

當運行到第1個await表達式後,建立一個新的線程,後臺執行該await表達式,實現異步。

第1個await表達式,未完成以前,繼續執行調用函數中的異步方法以後的代碼(注意await表達式後臺未完成以前,不是繼續執行await表達式以後的代碼,而是繼續執行調用函數中的異步方法以後的代碼),

當第1個await表達式在後臺完成後,繼續執行異步方法中第1個await表達式以後的代碼,

當運行到第2個await表達式後,建立一個新的線程,後臺運行該await表達式,

第2個await表達式,未完成以前,繼續執行調用函數中被第1個await完成後打斷的的代碼

當第2個await表達式在後臺運行完成後,繼續執行異步方法中第2個await表達式以後的代碼,

當異步方法運行到return後,則開始調用方法中的對該異步方法的延續任務

該延續任務和調用方法不在一個線程中,這裏有可能和第2個await表達式在同一個線程中,也有可能和第1個await表達式在同一個線程中。




3.取消一個異步操做

具體可參考:.net異步編程值任務並行庫-3.6取消異步操做

原理是同樣的,都是使用CancellationTokenCancellationTokenSource兩個類實現取消異步操做

看一個簡單的示例:

static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();//生成一個CancellationTokenSource對象,
            CancellationToken ct = cts.Token;//該對象能夠建立CancellationToken對象,即一個令牌(token)
            Task result = DoAsync(ct, 50);
            for (int i = 0; i <= 5; i++)//主線程中的循環(模擬在異步方法聲明以後的工做)
            {
                Thread.Sleep(1000);
                Console.WriteLine("主線程中的循環次數:" + i);
            }
            cts.Cancel();//注意在主線程中的循環結束後(5s左右),運行到此處,
                         //則此時CancellationTokenSource對象中的token.IsCancellationRequested==true
                         //則在異步操做DoAsync()中根據此判斷,則取消異步操做
            Console.ReadKey();
            CancellTask();

            CancellTask2();
        }

        //異步方法:取消異步操做的令牌經過參數傳入
        static async Task DoAsync(CancellationToken ct, int Max)
        {
            await Task.Run(() =>
            {
                for (int i = 0; i <= Max; i++)
                {
                    if (ct.IsCancellationRequested)//一旦CancellationToken對象的源CancellationTokenSource運行了Cancel();此時CancellationToken.IsCancellationRequested==ture
                    {
                        return;
                    }
                    Thread.Sleep(1000);
                    Console.WriteLine("次線程中的循環次數:" + i);
                }
            }/*,ct*/);//這裏取消令牌能夠做爲Task.Run()的第二個參數,也能夠不寫!
        }




4.同步和異步等待任務

4.1 在調用方法中同步等待任務

「調用方法能夠調用任意多個異步方法並接收它們返回的Task對象。而後你的代碼會繼續執行其餘任務,但在某個點上可能會須要等待某個特殊Task對象完成,而後再繼續。爲此,Task類提供了一個實例方法wait,能夠在Task對象上調用該方法。」--《C#圖解教程》

使用task.Wait();等待異步任務task完成:

Wait方法用於單一Task的對象。如果想要等待多個Task,可使用Task類中的兩個靜態方法,其中WaitAll等待全部任務都結束,WaitAny等待任一任務結束。

示例:使用Task.WaitAll()和Task.WaitAny()

static void Main(string[] args)
{
    Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前...");
    Task<int> t1 = DoAsync(2000);
    Task<int> t2 = DoAsync(6000);

    //Task.WaitAll(t1, t2);//等待t1和t2都完畢後才進行後續的代碼(即阻塞了主線程)
    //Task.WaitAny(t1, t2);//等待t1和t2中有任一個完成(調試的時候,你就會發現當t1完成後就開始執行後續的循代碼)

    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2}:循環中");
    }
    Console.ReadKey();
}

private static async Task<int> DoAsync(int num)
{
    int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"當前線程ID{Thread.CurrentThread.ManagedThreadId,2}:異步操做之等待:{num}s"); return num; });
     return result;
}

說明1 :

對代碼中註釋掉的代碼分別調試,則能夠發現其中的不一樣。

Task.WaitAll(params Task[] tasks):表示中止當前主線程,等待Task類型數組tasks中的全部Task操做結束,即會發生阻塞

Task.WaitAll()調試結果:

說明2:

Task.WaitAny(params Task[] tasks):表示中止當前主線程,等待Task類型數組tasks中的任一個Task操做結束,也是會發生阻塞,單是阻塞主線程在任一個Task操做結束,以後主線程會繼續,其餘的Task在後臺繼續

Task.WaitAny()調試結果:


4.2 在調用方法中異步等待任務

4.2.1使用await等待異步任務

其實在一個方法中調用多個異步方法時候,當某個異步方法依賴於另一個異步方法的結果的時候,咱們通常是在每個調用的異步方法處使用await關鍵字等待該異步操做的結果,可是這樣就會出現async傳染。

await不一樣於Wait(),await等待是異步的,不會阻塞線程,而Wait()會阻塞線程

注意如無必用,或是不存在對某個異步操做的等待,儘可能不要使用await,直接把異步操做的返回值給Task<T>類型的變量,可使程序運行的更快!

其實你也注意到了:不使用await等待異步操做,則異步操做的返回值就是定義的返回值Task<T>,可是使用await等待則異步操做的返回值就是具體的簡單類型,好比int類型等。
換言之:異步方法的返回值是Task<T>,則使用await等待能夠直接獲取異步方法的T類型的返回值

示例:

static void Main(string[] args)
{
  ReferencingMethodAsync();
}

//該調用函數也要使用async關鍵字修飾(即async傳染),由於使用了await等待,
private static async void ReferencingMethodAsync()
{
    int result1 = await SumAsync(1, 2);//這裏使用了await 關鍵字則,調用方法MultipleMethod2()必須使用async修飾(即async傳染性)
    int result2 = await SumAsync(1, result1);
    Console.WriteLine(result2);
}

private static async Task<int> SumAsync(int num1, int num2)
{
    int sum = await Task.Run(() => { Thread.Sleep(3000); return num1 + num2; });
    return sum;
}

4.2.2使用WhenAll()和WhenAny()

Task.WhenAll()Task.WhenAny()Task.WaitAll()Task.WaitAny()的異步版本,即異步等待Task完成

示例:使用Task.WhenAll()和Task.WhenAny()

static void Main(string[] args)
{
    Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前...");
    Task<int> t1 = DoAsync(2000);
    Task<int> t2 = DoAsync(6000);
    //Task.WhenAll(t1, t2);//異步等待t1和t2兩個完成(調試的時候你會發現任務t1和t2都在新的線程中執行,主線繼續執行後續的循環代碼)
    //Task.WhenAny(t1, t2);//異步等待t1和t2中任一個完成(調試的時候你就會發現兩個任務分別在新線程中執行,線程繼續執行後續的循環代碼,當t1完成後,繼續後續的循環代碼)
       
    for (int i = 0; i < 10; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2}:循環中");
    }
    Console.ReadKey();
}

private static async Task<int> DoAsync(int num)
{
    int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"當前線程ID{Thread.CurrentThread.ManagedThreadId,2}:異步操做之等待:{num}s"); return num; });
    return result;
}

說明1

在示例中看到Task.WhenAll和Task.WhenAny的使用,可是在實際中有什麼做用呢?

首先,如前所所述,Task.WhenAll()和Task.WhenAny()是Task.WaitAll()和Task.WaitAny()的異步版本,可是呢,Task.WaitAll()和Task.WaitAny()是沒有返回值的,Task.WhenAll()Task.WhenAny()是有返回值,返回值類型是一個Task對象,因此你能夠給其一個延續任務,即在異步等待的Task完成後,指定繼續執行的Task。

因此當調用的異步方法沒有相互的依賴的時候,通常仍是使用WhenAll(),來等待異步方法,同時也能夠給全部的異步方法結束後添加一個延續任務!

示例:爲異步等待後添加延續工做

static void Main(string[] args)
{
    Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:Task以前...");
    Task<int> t1 = DoAsync(2000);
    Task<int> t2 = DoAsync(6000);

    //Task.WhenAll(t1, t2).ContinueWith(t => Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:延續任務,兩個異步操做返回值是一個int[],其中元素分別是{t.Result[0]}、{t.Result[1]}"));

    //Task.WhenAny(t1, t2).ContinueWith(t => Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2 }:延續任務,第一個完成的異步操做返回值是{t.Result.Result}"));


    for (int i = 0; i < 8; i++)
    {
        Thread.Sleep(1000);
        Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2}:循環中");
    }
    Console.ReadKey();
}

private static async Task<int> DoAsync(int num)
{
    int result = await Task.Run(() => { Thread.Sleep(num); Console.WriteLine($"當前線程ID:{Thread.CurrentThread.ManagedThreadId,2}:異步操做之等待:{num}s"); return num; });
    return result;
}

說明1

如果Task.WhenAll()後的延續工做,則注意Task.WhenAll()的返回的Task<TResult>ResultTResult[]類型
即多個Task的返回值,存放在一個數組中

運行結果:

說明2:

如果Task.WhenAny()後的延續工做,則注意Task.WhenAny()的返回的是Task<Task>類型,即其ResultTask<TResutl>類型,因此爲了獲取第一結束的Task的返回值,須要:t.Result.Result

運行結果:

說明3

Task.WhenAll(Task[] tasks).ContinueWith(Action<Task>)
等價於Task.Factory.ContinueWhenAll(Task[] tasks, Action<Tast>)

Task.WhenAny(Task[] tasks).ContinueWith(Action<Task>)
等價於Task.Factory.ContinueWhenAny(Task[] tasks, Action<Tast>)




5.異步操做中的異常處理

5.1 異常處理

通常程序中對異常的處理使用try{……} catch{……}

首先看一個捕獲異常失敗的示例:

在Main()中調用ThrowEx(2000,"這是異常信息"),第一個參數是ThrowEx中的Tast延遲的時間,第二個參數是ThrowEx中的拋出異常的信息。

static void Main(string[] args)
{
    try
    {
        ThrowEx(2000, "這是異常信息");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    Console.ReadKey();
}
private async static Task ThrowEx(int ms, string message)//注意這裏的返回值類型爲Task,如果寫成void也是沒法在catch語句中捕獲異常,可是運行vs會報錯(見:說明2)
{
    await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word"));
    throw new Exception(message);
}

說明1:

多打斷點,就能夠發現爲什麼捕捉不到異常了。

由於當調用ThrowEx(2000, "異常信息"),開始異步方法中的await表達式,

即建立一個新的線程,在後臺執行await表達式,而主線程中此時會繼續執行ThrowEx(2000, "異常信息");後的代碼:catch (Exception ex)

此時,異步方法中還在等待await表達式的執行,尚未拋出咱們本身定義的異常,因此此時壓根就沒有異常拋出,因此catch語句也就捕獲不到異常,

而當異步方法拋出異常,此時主線程中catch語句已經執行完畢了!

說明2:

1.基本語法-返回值類型中咱們說道:在編寫異步方法的時候,有時後沒有返回值,也不須要查看異步操做的狀態,咱們設置返回值類型爲void,並且稱之爲「調用並忘記」。然而這種異步代碼編寫方式,並不值得提倡。

爲何呢?如果沒有返回值,異步方法中拋出的異常就沒法傳遞到主線程,在主線程中的catch語句就沒法捕獲拍異常!因此異步方法最好返回一個Task類型

異步方法有返回值的時候,拋出的在異常會置於Task對象中,能夠經過task.IsFlauted屬性查看是否有異常,在主線程的調用方法中,使用catch語句能夠捕獲異常!


正確示例:只須要給調用的異步方法,添加一個await

static void Main(string[] args)
{
    try
    {
       await ThrowEx(2000, "這是異常信息");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    Console.ReadKey();
}
private async static Task ThrowEx(int ms, string message)
{
    await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word"));
    throw new Exception(message);
}

5.2 多個異步方法的異常處理

使用Task.WhenAll()處理多個異步方法中拋出異常

當有多個異步操做,使用WhenAll異步等待,其返回值是一個Task類型對象,該對象的異常爲AggregateException類型的異常,每個的子任務(即WhenAll所等待的全部任務)拋出的異常都是包裝在這一個AggregateException中,如果須要打印其中的異常,則須要遍歷AggregateException.InnerExceptions

static void Main(string[] args)
{
    Task taskResult = null;//注意由於在catch語句中須要使用這個WhenAll的返回值,因此定義在try語句以外。
    try
    {
        Console.WriteLine($"當前的線程Id:{Thread.CurrentThread.ManagedThreadId,2}:do something before task");
        Task t1 = ThrowEx($"這是第一個拋出的異常信息:異常所在線程ID:{Thread.CurrentThread.ManagedThreadId,2}", 3000);
        Task t2 = ThrowEx($"這是第二個拋出的異常信息:異常所在線程ID:{Thread.CurrentThread.ManagedThreadId,2}", 5000);

        await (taskResult = Task.WhenAll(t1, t2));
    }
    catch (Exception)//注意這裏捕獲的異常只是WhenAll()等待的異步任務中第一拋出的異常
    {
        foreach (var item in taskResult.Exception.InnerExceptions)//經過WhenAll()的返回對象的Exception屬性來查閱全部的異常信息
        {
            Console.WriteLine($"當前的線程Id:{Thread.CurrentThread.ManagedThreadId,2}:{item.Message}");
        }
    }
}

private async static Task ThrowEx(int ms, string message)
{
    await Task.Delay(ms).ContinueWith(t => Console.WriteLine("hello word"));
    throw new Exception(message);
}

運行結果:

說明

Task.WhenAll()返回的Task對象中的Exception屬性是AggregateException類型的異常.

注意,該訪問該異常InnerExcption屬性則只包含第一個異常,該異常的InnerExcptions屬性,則包含全部子任務異常.

5.3 AggregateException中的方法

首先多個異步操做的異常會包裝在一個AggregateException異常中,被包裝的異常能夠也是AggregateException類型的異常,因此如果須要打印異常信息可能須要循環嵌套,比較麻煩。

故可使用 AggregateException.Flatten()打破異常的嵌套。

注意,凡是使用await等待的異步操做,它拋出的異常沒法使用catch(AggregateException)捕獲!

只能使用catch (Exception)對異常捕獲,在經過使用Task的返回值的Exception屬性對異性進行操做。

固然你要是想使用catch(AggregateException)捕獲到異常,則可使用task.Wait()方法等待異步任務,則拋出的異常爲AggregateException類型的異常

示例:(完整Demo

catch (AggregateException ae)//AggregateException類型異常的錯誤信息是「發生一個或多個異常」
{
    foreach (var exception in ae.Flatten().InnerExceptions)
    //使用AggregateException的Flatten()方法,除去異常的嵌套,這裏你也能夠測試不使用Flatten(),拋出的信息爲「有一個或多個異常」
    {
        if (exception is TestException)
        {
             Console.WriteLine(exception.Message);
        }
        else
        {
            throw;
        }
    }               
}

如果須要針對AggregateException中某個或是某種異常進行處理,可使用Handle()方法

Handel()的參數是一個有返回值的委託:Func<Exception,bool> predicate

示例:(完整Demo

catch (Exception)
{
    t.Exception.Handle(e =>
    {
        if (e is TestException)//若是是TestException類型的異常
        {
            Console.WriteLine(e.Message);
        }
        return e is TestException;
    });
}




6.多線程和異步的區分

不要把多線程異步兩個概念混爲一談!異步是最終目的,多線程只是咱們實現異步的一種手段!

首先,使用異步和多線程均可以免線程的阻塞,可是原理是不同的。

多線程:當前線程中建立一個新的線程,當前線程線程則不會被鎖定了,可是鎖定新的線程執行某個操做。換句話說就是換一條線程用來代替本來會被鎖定的主線程!優勢就是,線程中的處理程序的運行順序仍是從上往下的,方便理解,可是線程間的共享變量可能形成死鎖的出現。

異步:異步概念是與同步的概念相對的,簡單的說就是:調用者發送一個調用請求後,調用者不須要等待被被調用者返回結果而能夠繼續作其餘的事情。實現異步通常是經過多線程,可是還能夠經過多進程實現異步!

多線程和異步能夠解決不一樣的問題

可是首先咱們要區分當前須要長時間操做的任務是:CPU密集型仍是IO密集型,具體可參考長時間的複雜任務又分爲兩種

CPU Bound:使用多線程

IO Bound:使用異步




7. 在 .NET MVC中異步編程

如今的 ASP .NET MVC項目中,若使用的.net中的方法有異步版本的就儘可能使用異步的方法。

在MVC項目中異步編程能夠大大的提升網站服務器的吞吐量,便可以能夠大大的提升網站的同時受理的請求數量

據傳,MVC網站如果異步編程則能夠提升網站的同時訪問量的2.6倍。

注意是提升網站的同時訪問量,而不是提升網站的訪問速度!

在MVC項目異步編程的的方式和在控制檯中同樣,使用async和await,基於任務的異步編程模式

簡單的示例:同步和異步兩種方式分別讀取桌面1.txt文件

//同步操做
public ActionResult Index()
{
    string msg = "";
    using (StreamReader sr = new StreamReader(@"C:\Users\shanzm\Desktop\1.txt", Encoding.Default))
    {
        while (!sr.EndOfStream)
        {
            msg = sr.ReadToEnd();
        }
    }
    return Content(msg);
}


//異步操做
public async Task<ActionResult> Index2()
{
    string msg = "";
    using (StreamReader sr = new StreamReader(@"C:\Users\shanzm\Desktop\1.txt", Encoding.Default))
    {
        while (!sr.EndOfStream)
        {
            msg = await sr.ReadToEndAsync();//使用異步版本的方法
        }
    }
    return Content(msg);
}




8. 參考及示例源碼

源碼:點擊下載

書籍:C#高級編程

書籍:精通C#

微軟:基於任務的異步編程

微軟:異常處理(任務並行庫)

相關文章
相關標籤/搜索