## 使用C# 6.0中的async/await


異步函數是TPL之上更高級別的抽象,真正簡化了異步編程,它與普通函數不一樣在於必須有async標識,並且返回類型一般是Task<T>,Task類型,當然也可以使用async void,但更推薦使用async Task,使用async void唯一合理的地方在於程序中使用頂層UI控制器事件處理器的時候。

異步函數內部,必須至少有一個await操作符,該操作符一般與TPL一起工作,並獲取該任務中的異步操作的結果。需要注意的是在執行完await調用的代碼行后該方法會立即返回,剩下的異步函數的部分獨立異步運行,值得注意的是如果程序中有兩個連續的await操作符,它們是順序運行的,即第一個完成后,第二個才會開始運行。

結合線程的角度來理解,即,執行完await操作后,.NET會把當前的線程返回線程池,等異步方法調用執行完畢后((異步方法的代碼並不會在新線程中執行,除非把代碼放到新線程中執行,比如用Task.Run方法),框架再從線程池再取出來一個線程執行后續的代碼,把當前線程返回線程池就意味着具備並發的能力,就像餐廳模型中的服務員離開去歡迎其他客人一樣,當異步方法調用執行完畢后,再取出一個線程,就像又一個服務員過來繼續在await后的代碼處進行服務,運行,所以這次過來的服務員有可能是一開始的服務員,也有可能是別的服務員,所以此時的線程有可能是之前的線程,也有可能是其他線程。

還需要甄別是否是異步方法,
比如:

static async Task<int> GetPrimesCountAsync(int start,int count)
        {
            Console.WriteLine("GetPrimesCountAsync所在線程的ID:" + Thread.CurrentThread.ManagedThreadId);
            return Task.Run(()=>
            {
                Console.WriteLine("Run中所在的線程ID:"+Thread.CurrentThread.ManagedThreadId);
                return ParallelEnumerable.Range(start, count).Count(n =>
                           Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0));
            }
            );
}

其實並不是異步方法,因為await,asyn沒有成對,所以造成工作線程可以運行到 Console.WriteLine("GetPrimesCountAsync所在線程的ID:" + Thread.CurrentThread.ManagedThreadId);
直至遇到Task.Run,手動開啟一個新的任務。
await關鍵字簡化了連續性的附着(Simplify the attaching of continuations)。

比如:

var result=await expression;
statement(s);

等價於:

var awaiter=expression.GetAwaiter();
awaiter.OnCompleted(()=>{
    var result=awaiter.GetResult();
    statement(s);
});

上面的意思即為,主程序運行到await處,立即返回,返回給線程池,await中的表達式同時異步進行,並且約定,在表達式結束后,再調用statement(s),statement(s)就是運行在再從線程池中取出的新的線程上

可以用以下demo佐證:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadDemo
{
    class Program21
    {
        static async Task<string> GetInfoAsync(string name,int seconds)
        {
            //await Task.Delay(TimeSpan.FromSeconds(seconds));
            await Task.Run(() => { 
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine($"{name} first :{Thread.CurrentThread.ManagedThreadId}");
            });
            await Task.Run(() => {
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine($"{name} second:{Thread.CurrentThread.ManagedThreadId}");
            });
            return $"Task {name} is running on a thread id " +
                $"{Thread.CurrentThread.ManagedThreadId}. " +
                $"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
        }
        static async Task AsyncProcess()
        {
            Task<string> t1 = GetInfoAsync("Task 1", 3);
            Task<string> t2 = GetInfoAsync("Task 2", 5);
            string[] results = await Task.WhenAll(t1, t2);
            foreach(string result in results)
            {
                Console.WriteLine(result);
            }
        }
        static void Main()
        {
            Task t = AsyncProcess();
            t.Wait();
        }
    }
}

output:

Task 1 first :3
Task 2 first :4
Task 1 second:5
Task 2 second:3
Task Task 1 is running on a thread id 5. Is thread pool thread:True
Task Task 2 is running on a thread id 3. Is thread pool thread:True

這也說明了為什么上述GetInfoAsync返回的是Task<string>類型,因為await關鍵字后的任務后面的statement,實際上就是在最后一個await的任務的線程里完成的,所以return $"Task {name} is running on a thread id " + $"{Thread.CurrentThread.ManagedThreadId}. " + $"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";自然是異步中返回string的函數,即是Task<string>類型。

await最大的優勢在於可以出現在異步函數中的任意地方(除了lock,unsafe環境)。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program22
    {
        static async Task<string> GetInoAsync(string name,int seconds)
        {
            Console.WriteLine($"GetInfoAsync Pre:{Thread.CurrentThread.ManagedThreadId}");
            await Task.Delay(TimeSpan.FromSeconds(seconds));
            Console.WriteLine($"GetInfoAsync After:{Thread.CurrentThread.ManagedThreadId}");
            //throw new Exception($"Boom from {name}!");
            return $"In {Thread.CurrentThread.ManagedThreadId}";
        }
        static async Task AsyncProcess()
        {
            Console.WriteLine("1. Single exception");
            try
            {
                Console.WriteLine($"AsyncProcess Pre:{Thread.CurrentThread.ManagedThreadId}");
                string result = await GetInoAsync("Task 1", 2);
                Console.WriteLine(result);
                Console.WriteLine($"AsyncProcess after:{Thread.CurrentThread.ManagedThreadId}");
            }
            catch(Exception ex)
            {
                Console.WriteLine($"Exception details:{ex}");
            }
        }
        static void Main()
        {
            Console.WriteLine($"Main ID:{Thread.CurrentThread.ManagedThreadId}");
            Task t = AsyncProcess();
            t.Wait();
        }
    }
}

output:

Main ID:1
1. Single exception
AsyncProcess Pre:1
GetInfoAsync Pre:1
GetInfoAsync After:4
In 4
AsyncProcess after:4

在main()中,添加一個輸出:


static void Main()
        {
            Console.WriteLine($"Main ID:{Thread.CurrentThread.ManagedThreadId}");
            Task t = AsyncProcess();
            Console.WriteLine($"Before t.Wait(),Main ID is {Thread.CurrentThread.ManagedThreadId}");
            t.Wait();
        }

得到:

Main ID:1
1. Single exception
AsyncProcess Pre:1
GetInfoAsync Pre:1
Before t.Wait(),Main ID is 1
GetInfoAsync After:4
In 4
AsyncProcess after:4

上面的demo,清楚的顯示了線程隨着await的變化。

使用await操作符獲取異步任務結果

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program17
    {
        static async Task<string> GetInfoAsync(string name)
        {
            await Task.Delay(TimeSpan.FromSeconds(2));
            return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}" +
                $" Is thread pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
        }

        static Task AsynchronyWithTPL()
        {
            Task<string> t = GetInfoAsync("Task 1");
            Task t2 = t.ContinueWith(task => Console.WriteLine(t.Result), TaskContinuationOptions.NotOnFaulted);
            Task t3 = t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);
            return Task.WhenAny(t2, t3);
        }
        static async Task AsynchronyWithAwait()
        {
            try
            {
                string result = await GetInfoAsync("Task 2");
                Console.WriteLine(result);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
        static void Main()
        {
            Task t = AsynchronyWithTPL();
            t.Wait();

            t = AsynchronyWithAwait();
            t.Wait();
        }
    }
}

outputt:

Task Task 1 is running on a thread id 4 Is thread pool thread: True
Task Task 2 is running on a thread id 6 Is thread pool thread: True

如果將Task<String> GetInfoAsync函數改為:

        static async Task<string> GetInfoAsync(string name)
        {
            await Task.Delay(TimeSpan.FromSeconds(2));
            //return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}" +
            //    $" Is thread pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
            throw new Exception("Boom!");
        }

output:

System.Exception: Boom!
   在 ThreadDemo.Program17.<GetInfoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program17.cs:行號 16
System.Exception: Boom!
   在 ThreadDemo.Program17.<GetInfoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program17.cs:行號 16
--- 引發異常的上一位置中堆棧跟蹤的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   在 ThreadDemo.Program17.<AsynchronyWithAwait>d__2.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program17.cs:行號 30
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program17
    {
        static async Task<string> GetInfoAsync(string name)
        {
            await Task.Run(() => { Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running.."); });
            await Task.Delay(TimeSpan.FromSeconds(2));
            return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}" +
                $" Is thread pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
            //throw new Exception("Boom!");
        }

        static Task AsynchronyWithTPL()
        {
            Task<string> t = GetInfoAsync("Task 1");
            Task t2 = t.ContinueWith(task => Console.WriteLine(t.Result), TaskContinuationOptions.NotOnFaulted);
            Task t3 = t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);
            return Task.WhenAny(t2, t3);
        }
        static async Task AsynchronyWithAwait()
        {
            try
            {
                string result = await GetInfoAsync("Task 2");
                Console.WriteLine(result);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
        static void Main()
        {
            Task t = AsynchronyWithTPL();
            t.Wait();

            t = AsynchronyWithAwait();
            t.Wait();
        }
    }
}

output:

3 is running..
Task Task 1 is running on a thread id 4 Is thread pool thread: True
3 is running..
Task Task 2 is running on a thread id 6 Is thread pool thread: True

可以發現同一個異步函數內部,不同的await后的任務處理確實是在不同的線程池。

在lambda表達式中使用await操作符

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class program18
    {
        static async Task AsynchronousProcessing()
        {
            Func<string, Task<string>> asyncLambda = async name =>
               {
                   await Task.Delay(TimeSpan.FromSeconds(2));
                   return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}." +
                   $" Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
               };
            string result = await asyncLambda("async lambda");
            Console.WriteLine(result);
        }
        static void Main()
        {
            Task t = AsynchronousProcessing();
            t.Wait();
        }
    }
}

output:

Task async lambda is running on a thread id 4. Is thread pool thread:True

對連續的異步任務使用await操作符

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class program19
    {
        static async Task<string> GetInfoAsync(string name)
        {
            Console.WriteLine($"Task {name} started!");
            await Task.Delay(TimeSpan.FromSeconds(2));
            if(name=="TPL 2")
            {
                throw new Exception("Boom!");
            }
            return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}." +
                $" Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
        }

        static async Task AsyncronyWithAwait()
        {
            try
            {
                string result = await GetInfoAsync("Async 1");
                Console.WriteLine(result);
                result = await GetInfoAsync("Async 2");
                Console.WriteLine(result);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        static void Main()
        {
            Task t = AsyncronyWithAwait();
            t.Wait();
        }
    }
}

output:

Task Async 1 started!
Task Async 1 is running on a thread id 4. Is thread pool thread:True
Task Async 2 started!
Task Async 2 is running on a thread id 4. Is thread pool thread:True

對並行執行的異步任務使用await操作符

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadDemo
{
    class Program21
    {
        static async Task<string> GetInfoAsync(string name,int seconds)
        {
            await Task.Delay(TimeSpan.FromSeconds(seconds));
            //await Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(seconds)));
            return $"Task {name} is running on a thread id " +
                $"{Thread.CurrentThread.ManagedThreadId}. " +
                $"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
        }
        static async Task AsyncProcess()
        {
            Task<string> t1 = GetInfoAsync("Task 1", 3);
            Task<string> t2 = GetInfoAsync("Task 2", 5);
            string[] results = await Task.WhenAll(t1, t2);
            foreach(string result in results)
            {
                Console.WriteLine(result);
            }
        }
        static void Main()
        {
            Task t = AsyncProcess();
            t.Wait();
        }
    }
}

output:

Task Task 1 is running on a thread id 4. Is thread pool thread:True
Task Task 2 is running on a thread id 4. Is thread pool thread:True

我們使用了Task.WhenAll輔助方法創建了另一個任務,該任務的返回類型是Task<string[]>,該任務只有在所有底層任務完成后才會運行。5s后,我們可以觀察到,一瞬間全部出來了結果,這說明了這個任務在t1,t2都結束后,才執行。

但這里也觀察到一個非常有趣的現象:並行任務是被線程池同一個工作者線程執行的,How can it be?

為了搞清楚這個問題,我們需要知道Task.Delay的工作機理是怎么樣的!

它是使用類似這樣的技術實現的:

Task Delay(int milliseconds)
{
   var tcs=new TaskCompletionSource<object>();
    var timer=new System.Timers.Timer(milliseconds){AutoReset=false};//保證只調用一次
    timer.Elapsed+=delegate{timer.Dispose();tcs.SetResult(null);};
    timer.Start();
    return tcs.Task;
}

由此可見,對於GetInfoAsync異步方法,當timer所在的線程A達到milliseconds后,坍縮,由於await關鍵字,線程A將繼續執行return 語句,然后A結束,重新回到線程池,當t2再運行時,那么就很有可能重新從線程池中拾取到線程A,結果就造成了t1,t2都用了同一個worker thread。

如果使用await Task.Run(() => { Thread.Sleep(TimeSpan.FromSeconds(seconds)); 就肯定不會出現以上重復使用同一個worker thread的情況。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadDemo
{
    class Program21
    {
        static async Task<string> GetInfoAsync(string name,int seconds)
        {
            //await Task.Delay(TimeSpan.FromSeconds(seconds));
            await Task.Run(() => { 
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine($"{name} first :{Thread.CurrentThread.ManagedThreadId}");
            });
            await Task.Run(() => {
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine($"{name} second:{Thread.CurrentThread.ManagedThreadId}");
            });
            return $"Task {name} is running on a thread id " +
                $"{Thread.CurrentThread.ManagedThreadId}. " +
                $"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
        }
        static async Task AsyncProcess()
        {
            Task<string> t1 = GetInfoAsync("Task 1", 3);
            Task<string> t2 = GetInfoAsync("Task 2", 5);
            string[] results = await Task.WhenAll(t1, t2);
            foreach(string result in results)
            {
                Console.WriteLine(result);
            }
        }
        static void Main()
        {
            Task t = AsyncProcess();
            t.Wait();
        }
    }
}

output:

Task 1 first :3
Task 2 first :4
Task 1 second:5
Task 2 second:3
Task Task 1 is running on a thread id 5. Is thread pool thread:True
Task Task 2 is running on a thread id 3. Is thread pool thread:True

處理異步操作中的異常

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
    class Program22
    {
        static async Task<string> GetInoAsync(string name,int seconds)
        {
            Console.WriteLine($"GetInfoAsync Pre:{Thread.CurrentThread.ManagedThreadId}");
            await Task.Delay(TimeSpan.FromSeconds(seconds));
            Console.WriteLine($"GetInfoAsync After:{Thread.CurrentThread.ManagedThreadId}");
            throw new Exception($"Boom from {name}!");
            //return $"In {Thread.CurrentThread.ManagedThreadId}";
        }
        static async Task AsyncProcess()
        {
            Console.WriteLine("1. Single exception");
            try
            {
                Console.WriteLine($"AsyncProcess Pre:{Thread.CurrentThread.ManagedThreadId}");
                string result = await GetInoAsync("Task 1", 2);
                Console.WriteLine(result);
                Console.WriteLine($"AsyncProcess after:{Thread.CurrentThread.ManagedThreadId}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception details:{ex}");
            }
            Console.WriteLine();
            Console.WriteLine("2. Multiple exception");
            Console.WriteLine($"Multiple exception Pre:{Thread.CurrentThread.ManagedThreadId}");
            Task<string> t1 = GetInoAsync("Task 1", 3);
            Task<string> t2 = GetInoAsync("Task 2", 2);
            Console.WriteLine($"Multiple await Pre:{Thread.CurrentThread.ManagedThreadId}");
            try
            {
                string[] results = await Task.WhenAll(t1, t2);
                Console.WriteLine(results.Length);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception details:{ex}");
            }

            Console.WriteLine();
            Console.WriteLine("3. Multiple exceptions witth AggregateExceptioon");
            t1 = GetInoAsync("Task 1", 3);
            t2 = GetInoAsync("Task 2", 2);
            Task<string[]> t3 = Task.WhenAll(t1, t2);
            try
            {
                string[] results = await t3;
                Console.WriteLine(results.Length);
            }
            catch
            {
                var ae = t3.Exception.Flatten();
                var exceptions = ae.InnerExceptions;
                Console.WriteLine($"Exceptions caught:{exceptions.Count}");
                foreach (var e in exceptions)
                {
                    Console.WriteLine($"Exception details:{e}");
                    Console.WriteLine();
                }
            }
            Console.WriteLine();
            Console.WriteLine("4.await in catch and finally blocks");
            try
            {
                string result = await GetInoAsync("Task 1", 2);
                Console.WriteLine(result);
            }
            catch(Exception ex)
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                Console.WriteLine($"Catch block with await:Exceptioon details:{ex}");
            }
            finally
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                Console.WriteLine("Finally block");
            }
        }
        static void Main()
        {
            Console.WriteLine($"Main ID:{Thread.CurrentThread.ManagedThreadId}");
            Task t = AsyncProcess();
            t.Wait();
        }
    }
}

output:

Main ID:1
1. Single exception
AsyncProcess Pre:1
GetInfoAsync Pre:1
GetInfoAsync After:4
Exception details:System.Exception: Boom from Task 1!
   在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 16
--- 引發異常的上一位置中堆棧跟蹤的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   在 ThreadDemo.Program22.<AsyncProcess>d__1.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 25

2. Multiple exception
Multiple exception Pre:4
GetInfoAsync Pre:4
GetInfoAsync Pre:4
Multiple await Pre:4
GetInfoAsync After:4
GetInfoAsync After:5
Exception details:System.Exception: Boom from Task 1!
   在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 16
--- 引發異常的上一位置中堆棧跟蹤的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   在 ThreadDemo.Program22.<AsyncProcess>d__1.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 41

3. Multiple exceptions witth AggregateExceptioon
GetInfoAsync Pre:5
GetInfoAsync Pre:5
GetInfoAsync After:4
GetInfoAsync After:5
Exceptions caught:2
Exception details:System.Exception: Boom from Task 1!
   在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 16
--- 引發異常的上一位置中堆棧跟蹤的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   在 ThreadDemo.Program22.<AsyncProcess>d__1.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 56

Exception details:System.Exception: Boom from Task 2!
   在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 16


4.await in catch and finally blocks
GetInfoAsync Pre:5
GetInfoAsync After:4
Catch block with await:Exceptioon details:System.Exception: Boom from Task 1!
   在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 16
--- 引發異常的上一位置中堆棧跟蹤的末尾 ---
   在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   在 ThreadDemo.Program22.<AsyncProcess>d__1.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行號 74
Finally block

一個常見的錯誤就是,對一個以上的異步操作使用await時還使用以上方式。但如果仍像第一種情況一樣使用catch代碼塊,則只能從底層的AggregatetException對象中得到第一個異常,正如第二種情況表現的那樣。

為了收集所有異常信息,**可以使用await任務的Exception屬性,第三種情況,使用了AggreagateExceptionFlatten方法將層次異常放入一個列表,並從中提取出所有的底層異常。

避免使用捕獲的同步上下文

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace ThreadDemo
{
    class Program23
    {
        private static Label _label;
        static async void Click(object sender,EventArgs e)
        {
            _label.Content = new TextBlock() { Text = "Calculating..." };
            TimeSpan resultWithContext = await Test();
            TimeSpan resultNoContext = await TestNoContext();
            //TimeSpan resultNoContext = await TestNoContext().ConfigureAwait(false);
            StringBuilder sb = new StringBuilder();
            sb.AppendLine($"With the context:{resultWithContext}");
            sb.AppendLine($"Without the context:{resultNoContext}");
            sb.AppendLine("Ratio:" +
                $"{resultWithContext.TotalMilliseconds / resultNoContext.TotalMilliseconds:0.00}");
            _label.Content = new TextBlock() { Text = sb.ToString() };
        }
        [STAThread]
        static void Main()
        {
            Console.WriteLine($"Main Thread ID:{Thread.CurrentThread.ManagedThreadId}");
            Application app = new Application();
            Window win = new Window();
            StackPanel panel = new StackPanel();
            Button button = new Button();
            _label = new Label();
            _label.FontSize = 32;
            _label.Height = 200;
            button.Height = 200;
            button.FontSize = 32;
            button.Content = new TextBlock() { Text = "Start async operations" };
            button.Click += Click;
            panel.Children.Add(_label);
            panel.Children.Add(button);
            win.Content = panel;
            app.Run(win);
            Console.ReadLine();
        }
        static async Task<TimeSpan> Test()
        {
            const int iterationsNumber = 100000;
            var sw = new Stopwatch();
            sw.Start();
            for(int i = 0; i < iterationsNumber; i++)
            {
                var t = Task.Run(() =>{ });
                await t;
            }
            sw.Stop();
            return sw.Elapsed;
        }
        static async Task<TimeSpan> TestNoContext()
        {
            const int iterationsNumber = 100000;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for(int i = 0; i < iterationsNumber; i++)
            {
                Task t = Task.Run(() => { });
                await t.ConfigureAwait(continueOnCapturedContext: false);
            }
            sw.Stop();
            return sw.Elapsed;
        }
    }
}

可以看到常規的await操作符花費了更多的時間來完成,這是因為向UI線程中放入了成百上千個后續操作任務,這會使用它的消息循環來異步地執行這些任務,在本例中,我們無需再UI線程中運行該代碼,因為異步操作並未訪問UI組件

一個WPF的小例子

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button x:Name="btn" Click="btn_Click" Grid.Row="0">測試</Button>
        <TextBlock x:Name="textbox" Width="800" Height="200" Grid.Row="1"/>
        
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void btn_Click(object sender, RoutedEventArgs e)
        {
            btn.IsEnabled = false;
            string text = "";
            for (int i = 0; i < 10; i++)
            {
                text+="Call ID:" + Thread.CurrentThread.ManagedThreadId+"/n";
                //int count=await GetPrimesCountAsync(i * 1000000 + 2, 1000000);
                int count = GetPrimesCount(i * 1000000 + 2, 1000000);
                text += count.ToString() + "/n";
            }
            textbox.Text = text;
            btn.IsEnabled = true;
        }
        static Task<int> GetPrimesCountAsync(int start, int count)
        {
            return Task.Run(() => ParallelEnumerable.Range(start, count).Count(n =>
            Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
        }
        static int GetPrimesCount(int start,int count)
        {
            return Enumerable.Range(start, count).Count(n =>
            Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0));
        }
    }
}

可以發現,使用await后,點擊按鈕,並不阻塞主線程,而不使用await,點擊按鈕,阻塞主線程。
....未完待續


免責聲明!

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



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