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


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

0.背景引入

現在的.net異步編程中,一般都是使用 基於任務異步模式(Task-based Asynchronous Pattern,TAP)實現異步編程

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

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

配合着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異步程序設計之任務並行庫)

1.2 具體使用方法

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

  2. await:用於異步方法內部,用於指明需要異步執行的操作(稱之為await表達式),注意一個異步方法中可以有多個await表達式,而且應該至少有一個(若是沒有的話編譯的時候會警告,但還是可以構建和執行,只不過就相當於同步方法而已)。

    其實這里你有沒有想起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#

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM