本文將詳解C#類當中的Task,以及異步函數async await和Task的關係web
一開始咱們須要建立線程的時候通常是經過Thread建立線程,通常經常使用建立線程方式有如下幾種:編程
static void Main(string[] args) { Console.WriteLine("begin"); Thread thread = new Thread(() => TestMethod(2)); thread.IsBackground = true;//設置爲後臺線程,默認前臺線程 thread.Start(); Thread thread1 = new Thread(() => TestMethod1()); //設置thread1優先級爲最高,系統儘量單位時間內調度該線程,默認爲Normal thread1.Priority = ThreadPriority.Highest; thread1.Start(); Thread thread2 = new Thread((state) => TestMethod2(state)); thread2.Start("data"); thread2.Join();//等待thread2執行完成 Console.WriteLine("end"); } static void TestMethod(int a) { Thread.Sleep(1000); Console.WriteLine($"TestMethod: run on Thread id :{Thread.CurrentThread.ManagedThreadId},is threadPool:{Thread.CurrentThread.IsThreadPoolThread}" + $",is Backgound:{Thread.CurrentThread.IsBackground}, result:{a}"); } static void TestMethod1() { Thread.Sleep(1000); Console.WriteLine($"TestMethod1: run on Thread id :{Thread.CurrentThread.ManagedThreadId},is threadPool:{Thread.CurrentThread.IsThreadPoolThread}" + $",is Backgound:{Thread.CurrentThread.IsBackground},no result "); } static void TestMethod2(object state) { Thread.Sleep(2000); Console.WriteLine($"TestMethod2 :run on Thread id :{Thread.CurrentThread.ManagedThreadId},is threadPool:{Thread.CurrentThread.IsThreadPoolThread}" + $",is Backgound:{Thread.CurrentThread.IsBackground},result:{state}"); }
輸出結果:c#
begin TestMethod: run on Thread id :4,is threadPool:False,is Backgound:True, result:2 TestMethod1: run on Thread id :5,is threadPool:False,is Backgound:False,no result TestMethod2 :run on Thread id :7,is threadPool:False,is Backgound:False,result:data end
orwindows
begin TestMethod1: run on Thread id :5,is threadPool:False,is Backgound:False,no result TestMethod: run on Thread id :4,is threadPool:False,is Backgound:True, result:2 TestMethod2 :run on Thread id :7,is threadPool:False,is Backgound:False,result:data end
因爲個人PC是多核CPU,那麼TestMethod和TestMethod1所在兩個線程是真正並行的,因此有可能輸出結果前後不肯定,雖然TestMethod1所在線程設置優先級爲Highest最高,但可能系統不會優先調度,其實目前不怎麼推薦用Thread.Start去建立線程,缺點大概以下:api
除非是如下緣由:數組
真的須要操做線程優先級數據結構
須要建立一個前臺線程,因爲相似於控制檯程序當初始前臺線程執行完就會退出進程,那麼建立前臺線程能夠保證進程退出前該前臺線程正常執行成功多線程
例如在原來的例子註釋掉thread2.Join();,咱們會發現輸出完控制檯初始的前臺線程輸出完end沒退出進程,只有在TestMethod2(該線程凍結2秒最久)執行完才退出併發
static void Main(string[] args) { Console.WriteLine("begin"); Thread thread = new Thread(() => TestMethod(2)); thread.IsBackground = true;//設置爲後臺線程,默認前臺線程 thread.Start(); Thread thread1 = new Thread(() => TestMethod1()); //設置thread1優先級爲最高,系統儘量單位時間內調度該線程,默認爲Normal thread1.Priority = ThreadPriority.Highest; thread1.Start(); Thread thread2 = new Thread((state) => TestMethod2(state)); thread2.Start("data"); //thread2.Join();//等待thread2執行完成 Console.WriteLine("end"); }
輸出:app
begin end TestMethod1: run on Thread id :5,is threadPool:False,is Backgound:False,no result TestMethod: run on Thread id :4,is threadPool:False,is Backgound:True, result:2 TestMethod2 :run on Thread id :7,is threadPool:False,is Backgound:False,result:data
須要建立一個後臺線程,長時間執行的,其實一個Task的TaskScheduler在Default狀況下,設置TaskCreationOptions.LongRunning內部也是建立了一個後臺線程Thread,而不是在ThreadPool執行,在不須要Task的一些其餘功能狀況下,Thread更輕量
Thread longTask = new Thread(() => Console.WriteLine("doing long Task...")); longTask.IsBackground = true; longTask.Start(); //等價於 new Task(() => Console.WriteLine("doing long Task..."), TaskCreationOptions.LongRunning).Start(); //OR Task.Factory.StartNew(() => Console.WriteLine("doing long Task..."), TaskCreationOptions.LongRunning);
一個.NET進程中的CLR在進程初始化時,CLR會開闢一塊內存空間給ThreadPool,默認ThreadPool默認沒有線程,在內部會維護一個任務請求隊列,當這個隊列存在任務時,線程池則會經過開闢工做線程(都是後臺線程)去請求該隊列執行任務,任務執行完畢則回返回線程池,線程池儘量會用返回的工做線程去執行(減小開闢),若是沒返回線程池,則會開闢新的線程去執行,然後執行完畢又返回線程池,大概線程池模型以下:
咱們經過代碼來看:
static void Main(string[] args) { //獲取默認線程池容許開闢的最大工做線程樹和最大I/O異步線程數 ThreadPool.GetMaxThreads(out int maxWorkThreadCount, out int maxIOThreadCount); Console.WriteLine($"maxWorkThreadCount:{maxWorkThreadCount}, maxIOThreadCount:{maxIOThreadCount}"); //獲取默認線程池併發工做線程和I/O異步線程數 ThreadPool.GetMinThreads(out int minWorkThreadCount, out int minIOThreadCount); Console.WriteLine($"minWorkThreadCount:{minWorkThreadCount}, minIOThreadCount:{minIOThreadCount}"); for (int i = 0; i < 20; i++) { ThreadPool.QueueUserWorkItem(s => { var workThreadId = Thread.CurrentThread.ManagedThreadId; var isBackground = Thread.CurrentThread.IsBackground; var isThreadPool = Thread.CurrentThread.IsThreadPoolThread; Console.WriteLine($"work is on thread {workThreadId}, Now time:{DateTime.Now.ToString("ss.ff")}," + $" isBackground:{isBackground}, isThreadPool:{isThreadPool}"); Thread.Sleep(5000);//模擬工做線程運行 }); } Console.ReadLine(); }
輸出以下:
maxWorkThreadCount:32767,maxIOThreadCount:1000 minWorkThreadCount:16,minIOThreadCount:16 work is on thread 18, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 14, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 16, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 5, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 13, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 12, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 10, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 4, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 15, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 7, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 19, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 17, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 8, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 11, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 9, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 6, Now time:06.50, isBackground:True, isThreadPool:True work is on thread 20, Now time:07.42, isBackground:True, isThreadPool:True work is on thread 21, Now time:08.42, isBackground:True, isThreadPool:True work is on thread 22, Now time:09.42, isBackground:True, isThreadPool:True work is on thread 23, Now time:10.42, isBackground:True, isThreadPool:True
因爲我CPU爲8核16線程,默認線程池給我分配了16條工做線程和I/O線程,保證在該進程下實現真正的並行,能夠看到前16條工做線程的啓動時間是一致的,到最後四條,線程池嘗試去用以前的工做線程去請求那個任務隊列執行任務,因爲前16條還在運行沒返回到線程池,則每相隔一秒,建立新的工做線程去請求執行,並且該開闢的最多線程數是和線程池容許開闢的最大工做線程樹和最大I/O異步線程數有關的
咱們能夠經過ThreadPool.SetMaxThreads 將工做線程數設置最多隻有16,在執行任務前新增幾行代碼:
var success = ThreadPool.SetMaxThreads(16, 16);//只能設置>=最小併發工做線程數和I/O線程數 Console.WriteLine($"SetMaxThreads success:{success}"); ThreadPool.GetMaxThreads(out int maxWorkThreadCountNew, out int maxIOThreadCountNew); Console.WriteLine($"maxWorkThreadCountNew:{maxWorkThreadCountNew}, maxIOThreadCountNew:{maxIOThreadCountNew}");
輸出以下:
maxWorkThreadCount:32767,maxIOThreadCount:1000 minWorkThreadCount:16,minIOThreadCount:16 SetMaxThreads success:True maxWorkThreadCountNew:16,maxIOThreadCountNew:16 work is on thread 6, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 12, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 7, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 8, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 16, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 10, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 15, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 13, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 11, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 4, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 9, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 19, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 17, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 5, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 14, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 18, Now time:01.71, isBackground:True, isThreadPool:True work is on thread 8, Now time:06.72, isBackground:True, isThreadPool:True work is on thread 5, Now time:06.72, isBackground:True, isThreadPool:True work is on thread 19, Now time:06.72, isBackground:True, isThreadPool:True work is on thread 10, Now time:06.72, isBackground:True, isThreadPool:True
能夠很清楚知道,因爲線程池最多隻容許開闢16條工做線程和I/O線程,那麼在線程池再開闢了16條線程以後,將不會再開闢新線程,新的任務也只能等前面的工做線程執行完回線程池後,再用返回的線程去執行新任務,致使新任務的開始執行時間會在5秒後
ThreadPool的優勢以下:
但一樣,缺點也很明顯:
在.NET 4.0時候,引入了任務並行庫,也就是所謂的TPL(Task Parallel Library),帶來了Task
類和支持返回值的Task<TResult>
,同時在4.5完善優化了使用,Task解決了上述Thread和ThreadPool的一些問題,Task到底是個啥,咱們來看下代碼:
如下是一個WPF的應用程序,在Button的Click事件:
private void Button_Click(object sender, RoutedEventArgs e) { Task.Run(() => { var threadId = Thread.CurrentThread.ManagedThreadId; var isBackgound = Thread.CurrentThread.IsBackground; var isThreadPool = Thread.CurrentThread.IsThreadPoolThread; Thread.Sleep(3000);//模擬耗時操做 Debug.WriteLine($"task1 work on thread:{threadId},isBackgound:{isBackgound},isThreadPool:{isThreadPool}"); }); new Task(() => { var threadId = Thread.CurrentThread.ManagedThreadId; var isBackgound = Thread.CurrentThread.IsBackground; var isThreadPool = Thread.CurrentThread.IsThreadPoolThread; Thread.Sleep(3000);//模擬耗時操做 Debug.WriteLine($"task2 work on thread:{threadId},isBackgound:{isBackgound},isThreadPool:{isThreadPool}"); }).Start(TaskScheduler.FromCurrentSynchronizationContext()); Task.Factory.StartNew(() => { var threadId = Thread.CurrentThread.ManagedThreadId; var isBackgound = Thread.CurrentThread.IsBackground; var isThreadPool = Thread.CurrentThread.IsThreadPoolThread; Thread.Sleep(3000);//模擬耗時操做 Debug.WriteLine($"task3 work on thread:{threadId},isBackgound:{isBackgound},isThreadPool:{isThreadPool}"); }, TaskCreationOptions.LongRunning); }
輸出:
main thread id :1 //因爲是並行,輸出結果的先後順序可能每次都不同 task1 work on thread:4,isBackgound:True,isThreadPool:True task3 work on thread:10,isBackgound:True,isThreadPool:False task2 work on thread:1,isBackgound:False,isThreadPool:False
我用三種不一樣的Task開闢運行任務的方式,能夠看到,Task運行在三種不一樣的線程:
所以,其實Task不必定表明開闢了新線程,可爲在線程池上運行,又或是開闢一個後臺Thread,又或者沒有開闢線程,經過主線程運行任務,這裏提一句TaskScheduler.FromCurrentSynchronizationContext(),假設在控制檯或者ASP.NET Core程序運行,會發生報錯,緣由是主線程的SynchronizationContext爲空,可經過TaskScheduler源碼得知:
public static TaskScheduler FromCurrentSynchronizationContext() { return new SynchronizationContextTaskScheduler(); } internal SynchronizationContextTaskScheduler() { m_synchronizationContext = SynchronizationContext.Current ?? throw new InvalidOperationException (SR.TaskScheduler_FromCurrentSynchronizationContext_NoCurrent); }
大體對於Task在經過TaskScheduler和TaskCreationOptions設置後對於將任務分配在不一樣的線程狀況,以下圖:
Task其實有兩種延續任務的方式,一種經過ContinueWith方法,這是Task在.NET Framework4.0就支持的,一種則是經過GetAwaiter方法,則是在.NET Framework4.5開始支持,並且該方法也是async await異步函數所用到
控制檯代碼:
static void Main(string[] args) { Task.Run(() => { Console.WriteLine($"ContinueWith:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); return 25; }).ContinueWith(t => { Console.WriteLine($"ContinueWith Completed:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); Console.WriteLine($"ContinueWith Completed:{t.Result}"); }); //等價於 var awaiter = Task.Run(() => { Console.WriteLine($"GetAwaiter:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); return 25; }).GetAwaiter(); awaiter.OnCompleted(() => { Console.WriteLine($"GetAwaiter Completed:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); Console.WriteLine($"GetAwaiter Completed:{awaiter.GetResult()}"); }); Console.ReadLine(); }
輸出結果:
ContinueWith:threadId:4,isThreadPool:True GetAwaiter:threadId:5,isThreadPool:True GetAwaiter Completed:threadId:5,isThreadPool:True GetAwaiter Completed:25 ContinueWith Completed:threadId:4,isThreadPool:True ContinueWith Completed:25 //事實上,運行的代碼線程,可能和延續的線程有可能不是同一線程,取決於線程池自己的調度 能夠手動設置TaskContinuationOptions.ExecuteSynchronously(同一線程) 或者 TaskContinuationOptions.RunContinuationsAsynchronously(不一樣線程) 默認RunContinuationsAsynchronously優先級大於ExecuteSynchronously
但有意思的是,一樣的代碼,在WPF/WinForm等程序,運行的輸出是不同的:
WPF程序代碼:
private void Button_Click(object sender, RoutedEventArgs e) { Task.Run(() => { Debug.WriteLine($"ContinueWith:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); }).ContinueWith(t => { Debug.WriteLine($"ContinueWith Completed:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); }, TaskContinuationOptions.ExecuteSynchronously); Task.Run(() => { Debug.WriteLine($"GetAwaiter:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); }).GetAwaiter().OnCompleted(() => { Debug.WriteLine($"GetAwaiter Completed:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); }); }
輸出:
ContinueWith:threadId:7,isThreadPool:True GetAwaiter:threadId:9,isThreadPool:True ContinueWith Completed:threadId:7,isThreadPool:True GetAwaiter Completed:threadId:1,isThreadPool:False
緣由就是GetAwaiter().OnCompleted()會去檢測有沒有SynchronizationContext,所以其實就是至關於如下代碼:
Task.Run(() => { Debug.WriteLine($"GetAwaiter:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); }).ContinueWith(t => { Debug.WriteLine($"GetAwaiter Completed:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); },TaskScheduler.FromCurrentSynchronizationContext());
若是在WPF程序中要得到控制檯那樣效果,只須要修改成ConfigureAwait(false),延續任務不在SynchronizationContext便可,以下:
Task.Run(() => { Debug.WriteLine($"GetAwaiter:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); }).ConfigureAwait(false).GetAwaiter().OnCompleted(() => { Debug.WriteLine($"GetAwaiter Completed:threadId:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); });
在.NET Framework4.0帶來Task的同時,一樣帶來了與取消任務有關的類CancellationTokenSource和CancellationToken,下面咱們將大體演示下其用法
WPF程序代碼以下:
CancellationTokenSource tokenSource; private void BeginButton_Click(object sender, RoutedEventArgs e) { tokenSource = new CancellationTokenSource(); LongTask(tokenSource.Token); } private void CancelButton_Click(object sender, RoutedEventArgs e) { tokenSource?.Cancel(); } private void LongTask(CancellationToken cancellationToken) { Task.Run(() => { for (int i = 0; i < 10; i++) { Dispatcher.Invoke(() => { this.tbox.Text += $"now is {i} \n"; }); Thread.Sleep(1000); if (cancellationToken.IsCancellationRequested) { MessageBox.Show("取消了該操做"); return; } } }, cancellationToken); }
效果以下:
其實上述代碼,也能夠適用於Thread和ThreadPool,等價於以下代碼:
//當TaskCreationOptions爲LongRunning和默認TaskScheduler狀況下 new Thread(() => { for (int i = 0; i < 10; i++) { Dispatcher.Invoke(() => { this.tbox.Text += $"now is {i} \n"; }); Thread.Sleep(1000); if (cancellationToken.IsCancellationRequested) { MessageBox.Show("取消了該操做"); return; } } }).Start(); //默認TaskScheduler狀況下 ThreadPool.QueueUserWorkItem(t => { for (int i = 0; i < 10; i++) { Dispatcher.Invoke(() => { this.tbox.Text += $"now is {i} \n"; }); Thread.Sleep(1000); if (cancellationToken.IsCancellationRequested) { MessageBox.Show("取消了該操做"); return; } } });
所以,.NET Framework4.0後Thread和ThreadPool也一樣可以經過CancellationTokenSource和CancellationToken類支持取消功能,只是通常這二者均可以用Task經過設置,底層一樣調用的Thread和ThreadPool,因此通常沒怎麼這麼使用,並且關於Task的基本不少方法都默認支持了,例如,Task.Wait、Task.WaitAll、Task.WaitAny、Task.WhenAll、Task.WhenAny、Task.Delay等等
下面控制檯代碼:
static void Main(string[] args) { var parent = Task.Factory.StartNew(() => { int[] numbers = { 0 }; var childFactory = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None); childFactory.StartNew(() => 5 / numbers[0]); // Division by zero childFactory.StartNew(() => numbers[1]); // Index out of range childFactory.StartNew(() => { throw null; }); // Null reference }); try { parent.Wait(); } catch (AggregateException aex) { foreach (var item in aex.InnerExceptions) { Console.WriteLine(item.InnerException.Message.ToString()); } } Console.ReadLine(); }
輸出以下:
嘗試除以零。 索引超出了數組界限。 未將對象引用設置到對象的實例。
這裏面parent任務有三個子任務,三個並行子任務分別都拋出不一樣異常,返回到parent任務中,而當你對parent任務Wait或者獲取其Result屬性時,那麼將會拋出異常,而使用AggregateException則能將所有異常放在其InnerExceptions異常列表中,咱們則能夠分別對不一樣異常進行處理,這在多任務並行時候是很是好用的,並且AggregateException的功能異常強大,遠遠不止上面的功能,可是若是你只是單任務,使用AggregateException比普通則其實會有浪費性能,也能夠這樣作;
try { var task = Task.Run(() => { string str = null; str.ToLower(); return str; }); var result = task.Result; } catch (Exception ex) { Console.WriteLine(ex.Message.ToString()); } //或者經過async await try { var result = await Task.Run(() => { string str = null; str.ToLower(); return str; }); catch (Exception ex) { Console.WriteLine(ex.Message.ToString()); }
輸出:
未將對象引用設置到對象的實例。
async await是C#5.0,也就是.NET Framework 4.5時期推出的C#語法,經過與.NET Framework 4.0時引入的任務並行庫,也就是所謂的TPL(Task Parallel Library)構成了新的異步編程模型,也就是TAP(Task-based asynchronous pattern),基於任務的異步模式
咱們先來寫下代碼,看看async await的用法:
下面是個控制檯的代碼:
static async Task Main(string[] args) { var result = await Task.Run(() => { Console.WriteLine($"current thread:{Thread.CurrentThread.ManagedThreadId}," + $"isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); Thread.Sleep(1000); return 25; }); Console.WriteLine($"current thread:{Thread.CurrentThread.ManagedThreadId}," + $"isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); Console.WriteLine(result); Console.ReadLine(); }
輸出結果:
current thread:4,isThreadPool:True current thread:4,isThreadPool:True 25
換成在WPF/WinForm程序執行,結果以下:
current thread:4,isThreadPool:True current thread:1,isThreadPool:false 25
是否是感受似曾相識?上面埋下的彩蛋在這裏揭曉了,在講Task的延續的時候咱們講到.NET Framework4.5的一種經過GetAwaiter延續方法,事實上,async await就是上面的一種語法糖,編譯的時候大體會編譯成那樣,因此咱們通常不手動寫GetAwaiter的延續方法,而是經過async await,大大簡化了編程方式,說它是語法糖,那麼有啥證據呢?
咱們再寫一些代碼來驗證:
class Program { static void Main(string[] args) { ShowResult(classType: typeof(Program), methodName: nameof(AsyncTaskResultMethod)); ShowResult(classType: typeof(Program), methodName: nameof(AsyncTaskMethod)); ShowResult(classType: typeof(Program), methodName: nameof(AsyncVoidMethod)); ShowResult(classType: typeof(Program), methodName: nameof(RegularMethod)); Console.ReadKey(); } public static async Task<int> AsyncTaskResultMethod() { return await Task.FromResult(5); } public static async Task AsyncTaskMethod() { await new TaskCompletionSource<int>().Task; } public static async void AsyncVoidMethod() { } public static int RegularMethod() { return 5; } private static bool IsAsyncMethod(Type classType, string methodName) { MethodInfo method = classType.GetMethod(methodName); Type attType = typeof(AsyncStateMachineAttribute); var attrib = (AsyncStateMachineAttribute)method.GetCustomAttribute(attType); return (attrib != null); } private static void ShowResult(Type classType, string methodName) { Console.Write((methodName + ": ").PadRight(16)); if (IsAsyncMethod(classType, methodName)) Console.WriteLine("Async method"); else Console.WriteLine("Regular method"); } }
輸出:
AsyncTaskResultMethod: Async method AsyncTaskMethod: Async method AsyncVoidMethod: Async method RegularMethod: Regular method
在這其中,其實async在方法名的時候,只容許,返回值爲void,Task,Task
internal class Program { [CompilerGenerated] private sealed class <AsyncTaskResultMethod>d__1 : IAsyncStateMachine { public int <>1__state; public AsyncTaskMethodBuilder<int> <>t__builder; private int <>s__1; private TaskAwaiter<int> <>u__1; void IAsyncStateMachine.MoveNext() { int num = this.<>1__state; int result; try { TaskAwaiter<int> awaiter; if (num != 0) { awaiter = Task.FromResult<int>(5).GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = 0; this.<>u__1 = awaiter; Program.<AsyncTaskResultMethod>d__1 <AsyncTaskResultMethod>d__ = this; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, Program.<AsyncTaskResultMethod>d__1>(ref awaiter, ref <AsyncTaskResultMethod>d__); return; } } else { awaiter = this.<>u__1; this.<>u__1 = default(TaskAwaiter<int>); this.<>1__state = -1; } this.<>s__1 = awaiter.GetResult(); result = this.<>s__1; } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(result); } [DebuggerHidden] void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { } } [CompilerGenerated] private sealed class <AsyncTaskMethod>d__2 : IAsyncStateMachine { public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; private TaskAwaiter<int> <>u__1; void IAsyncStateMachine.MoveNext() { int num = this.<>1__state; try { TaskAwaiter<int> awaiter; if (num != 0) { awaiter = new TaskCompletionSource<int>().Task.GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = 0; this.<>u__1 = awaiter; Program.<AsyncTaskMethod>d__2 <AsyncTaskMethod>d__ = this; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, Program.<AsyncTaskMethod>d__2>(ref awaiter, ref <AsyncTaskMethod>d__); return; } } else { awaiter = this.<>u__1; this.<>u__1 = default(TaskAwaiter<int>); this.<>1__state = -1; } awaiter.GetResult(); } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } [DebuggerHidden] void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { } } private sealed class <AsyncVoidMethod>d__3 : IAsyncStateMachine { public int <>1__state; public AsyncVoidMethodBuilder <>t__builder; void IAsyncStateMachine.MoveNext() { int num = this.<>1__state; try { } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } [DebuggerHidden] void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { } } [DebuggerStepThrough, AsyncStateMachine(typeof(Program.<AsyncTaskResultMethod>d__1))] public static Task<int> AsyncTaskResultMethod() { Program.<AsyncTaskResultMethod>d__1 <AsyncTaskResultMethod>d__ = new Program.<AsyncTaskResultMethod>d__1(); <AsyncTaskResultMethod>d__.<>t__builder = AsyncTaskMethodBuilder<int>.Create(); <AsyncTaskResultMethod>d__.<>1__state = -1; <AsyncTaskResultMethod>d__.<>t__builder.Start<Program.<AsyncTaskResultMethod>d__1>(ref <AsyncTaskResultMethod>d__); return <AsyncTaskResultMethod>d__.<>t__builder.Task; } [DebuggerStepThrough, AsyncStateMachine(typeof(Program.<AsyncTaskMethod>d__2))] public static Task AsyncTaskMethod() { Program.<AsyncTaskMethod>d__2 <AsyncTaskMethod>d__ = new Program.<AsyncTaskMethod>d__2(); <AsyncTaskMethod>d__.<>t__builder = AsyncTaskMethodBuilder.Create(); <AsyncTaskMethod>d__.<>1__state = -1; <AsyncTaskMethod>d__.<>t__builder.Start<Program.<AsyncTaskMethod>d__2>(ref <AsyncTaskMethod>d__); return <AsyncTaskMethod>d__.<>t__builder.Task; } [DebuggerStepThrough, AsyncStateMachine(typeof(Program.<AsyncVoidMethod>d__3))] public static void AsyncVoidMethod() { Program.<AsyncVoidMethod>d__3 <AsyncVoidMethod>d__ = new Program.<AsyncVoidMethod>d__3(); <AsyncVoidMethod>d__.<>t__builder = AsyncVoidMethodBuilder.Create(); <AsyncVoidMethod>d__.<>1__state = -1; <AsyncVoidMethod>d__.<>t__builder.Start<Program.<AsyncVoidMethod>d__3>(ref <AsyncVoidMethod>d__); } public static int RegularMethod() { return 5; } }
咱們大體來捋一捋,事實上,從反編譯後的代碼能夠看出來一些東西了,編譯器大體是這樣的,以AsyncTaskResultMethod方法爲例子:
實際上,上述只是編譯器爲async作的事情,咱們能夠看到經過AsyncVoidMethod方法編譯器生成的東西和其餘方法大體同樣,那麼await爲編譯器作的就是MoveNext方法裏面try那段,這也是AsyncVoidMethod方法和其餘方法不一致的地方:
private TaskAwaiter<int> <>u__1; try { TaskAwaiter<int> awaiter; if (num != 0) { awaiter = new TaskCompletionSource<int>().Task.GetAwaiter(); if (!awaiter.IsCompleted) { this.<>1__state = 0; this.<>u__1 = awaiter; Program.<AsyncTaskMethod>d__2 <AsyncTaskMethod>d__ = this; this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, Program.<AsyncTaskMethod>d__2>(ref awaiter, ref <AsyncTaskMethod>d__); return; } } else { awaiter = this.<>u__1; this.<>u__1 = default(TaskAwaiter<int>); this.<>1__state = -1; } awaiter.GetResult(); }
咱們再看看this.<>t__builder.AwaitUnsafeOnCompleted內部:
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { try { AsyncMethodBuilderCore.MoveNextRunner runner = null; Action completionAction = this.m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runner); if (this.m_coreState.m_stateMachine == null) { Task<TResult> task = this.Task; this.m_coreState.PostBoxInitialization(stateMachine, runner, task); } awaiter.UnsafeOnCompleted(completionAction); } catch (Exception exception) { AsyncMethodBuilderCore.ThrowAsync(exception, null); } }
GetCompletionAction方法內部:
[SecuritySafeCritical] internal Action GetCompletionAction(Task taskForTracing, ref AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize) { Debugger.NotifyOfCrossThreadDependency(); ExecutionContext executionContext = ExecutionContext.FastCapture(); Action action; AsyncMethodBuilderCore.MoveNextRunner moveNextRunner; if (executionContext != null && executionContext.IsPreAllocatedDefault) { action = this.m_defaultContextAction; if (action != null) { return action; } moveNextRunner = new AsyncMethodBuilderCore.MoveNextRunner(executionContext, this.m_stateMachine); action = new Action(moveNextRunner.Run); if (taskForTracing != null) { action = (this.m_defaultContextAction = this.OutputAsyncCausalityEvents(taskForTracing, action)); } else { this.m_defaultContextAction = action; } } else { moveNextRunner = new AsyncMethodBuilderCore.MoveNextRunner(executionContext, this.m_stateMachine); action = new Action(moveNextRunner.Run); if (taskForTracing != null) { action = this.OutputAsyncCausalityEvents(taskForTracing, action); } } if (this.m_stateMachine == null) { runnerToInitialize = moveNextRunner; } return action; } void moveNextRunner.Run() { if (this.m_context != null) { try { ContextCallback contextCallback = AsyncMethodBuilderCore.MoveNextRunner.s_invokeMoveNext; if (contextCallback == null) { contextCallback = (AsyncMethodBuilderCore.MoveNextRunner.s_invokeMoveNext = new ContextCallback(AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext)); } ExecutionContext.Run(this.m_context, contextCallback, this.m_stateMachine, true); return; } finally { this.m_context.Dispose(); } } this.m_stateMachine.MoveNext(); }
從上面的代碼能夠看出,其實this.<>t__builder.AwaitUnsafeOnCompleted內部就作了如下:
大體執行流程圖以下:
所以,咱們驗證了async await確實是語法糖,編譯器爲其在背後作了太多的事情,簡化了咱們編寫異步代碼的方式,咱們也注意到了其中一些問題:
確實如猜測的,像await Task.Yield()等等,被await的對象,它必須包含如下條件:
有一個GetAwaiter方法,爲實例方法或者擴展方法
GetAwaiter方法的返回值類,必須包含如下條件
直接或者間接實現INotifyCompletion接口,ICriticalNotifyCompletion也繼承自ICriticalNotifyCompletion接口,也就是實現了其UnsafeOnCompleted或者OnCompleted方法
有個布爾屬性IsCompleted,且get開放
有個GetResult方法,返回值爲void或者TResult
所以能夠自定義一些能被await的類,關於如何自定義的細節,能夠參考林德熙大佬的這篇文章:C# await 高級用法
事實上,咱們在線程池上還埋下一個彩蛋,線程池上有工做線程適合CPU密集型操做,還有I/O完成端口線程適合I/O密集型操做,而async await異步函數實際上的主場是在I/O密集型這裏,咱們先經過一段代碼
static void Main(string[] args) { ThreadPool.SetMaxThreads(8, 8);//設置線程池最大工做線程和I/O完成端口線程數量 Read(); Console.ReadLine(); } static void Read() { byte[] buffer; byte[] buffer1; FileStream fileStream = new FileStream("E:/test1.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 10000, useAsync: true); buffer = new byte[fileStream.Length]; var state = Tuple.Create(buffer, fileStream); FileStream fileStream1 = new FileStream("E:/test2.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 10000, useAsync: true); buffer1 = new byte[fileStream1.Length]; var state1 = Tuple.Create(buffer1, fileStream1); fileStream.BeginRead(buffer, 0, (int)fileStream.Length, EndReadCallback, state); fileStream1.BeginRead(buffer, 0, (int)fileStream1.Length, EndReadCallback, state1); } static void EndReadCallback(IAsyncResult asyncResult) { Console.WriteLine("Starting EndWriteCallback."); Console.WriteLine($"current thread:{Thread.CurrentThread.ManagedThreadId},isThreadPool:{Thread.CurrentThread.IsThreadPoolThread}"); try { var state = (Tuple<byte[], FileStream>)asyncResult.AsyncState; ThreadPool.GetAvailableThreads(out int workerThreads, out int portThreads); Console.WriteLine($"AvailableworkerThreads:{workerThreads},AvailableIOThreads:{portThreads}"); state.Item2.EndRead(asyncResult); } finally { Console.WriteLine("Ending EndWriteCallback."); } }
輸出結果:
Starting EndWriteCallback. current thread:3,isThreadPool:True AvailableworkerThreads:8,AvailableIOThreads:7 Ending EndWriteCallback. Starting EndWriteCallback. current thread:3,isThreadPool:True AvailableworkerThreads:8,AvailableIOThreads:7 Ending EndWriteCallback.
咱們看到,事實上,兩個回調方法都調用了相同的線程,且是線程池的I/O完成端口線程,假如將兩個實例化FileStream時的參數改下,改成useAsync: false,輸出結果以下:
Starting EndWriteCallback. current thread:4,isThreadPool:True AvailableworkerThreads:6,AvailableIOThreads:8 Ending EndWriteCallback. Starting EndWriteCallback. current thread:5,isThreadPool:True AvailableworkerThreads:7,AvailableIOThreads:8 Ending EndWriteCallback.
咱們會發現此次用到的是線程池的兩條工做線程了,其實這就是同步I/O和異步I/O的區別,咱們能夠大概看下最底層BeginRead代碼:
private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) { if (bytes.Length - offset < count) { throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition")); } if (bytes.Length == 0) { hr = 0; return 0; } int num = 0; int numBytesRead = 0; fixed (byte* ptr = bytes) { num = ((!_isAsync) ? Win32Native.ReadFile(handle, ptr + offset, count, out numBytesRead, IntPtr.Zero) : Win32Native.ReadFile(handle, ptr + offset, count, IntPtr.Zero, overlapped)); } if (num == 0) { hr = Marshal.GetLastWin32Error(); if (hr == 109 || hr == 233) { return -1; } if (hr == 6) { _handle.Dispose(); } return -1; } hr = 0; return numBytesRead; }
實際上底層是Pinvoke去調用win32api ,Win32Native.ReadFile,關於該win32函數細節可參考MSDN:ReadFile,是否異步的關鍵就是判斷是否傳入overlapped對象,而該對象會關聯到一個window內核對象,IOCP(I/O Completion Port),也就是I/O完成端口,事實上進程建立的時候,建立線程池的同時就會建立這麼一個I/O完成端口內核對象,大體流程以下:
那麼在多請求的時候,IOCP模型異步的這種狀況,少許的I/O完成端口線程就能作到這一切,而同步則要由於一條線程要等待該請求處理的完成,那麼會大大浪費線程,正如上面同樣,兩個請求卻要兩個工做線程完成通知,而在async await時期,上面的一些方法已經被封裝以Task
和Task<TResult>
對象來表明完成讀取了,那麼上面能夠簡化爲:
static async Task Main(string[] args) { ThreadPool.SetMaxThreads(8, 8);//設置線程池最大工做線程和I/O完成端口線程數量 await ReadAsync(); Console.ReadLine(); } static async Task<int> ReadAsync() { FileStream fileStream = new FileStream("E:/test1.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 10000, useAsync: true); var buffer = new byte[fileStream.Length]; var result = await fileStream.ReadAsync(buffer, 0, (int)fileStream.Length); return result; }
底層沒變,只是回調的時候I/O完成端口線程再經過工做線程進行回調(這能避免以前回調的時候阻塞I/O完成端口線程的操做),可是大大的簡化了異步I/O編程,而async await並不是不適合CPU密集型,只是I/O操做通常比較耗時,若是用線程池的工做線程,就會有可能建立更多線程來應付更多的請求,CPU密集型的任務並行庫 (TPL)有不少合適的api
咱們瞭解了Task是.NET 編寫多線程的一個很是方便的高層抽象類,你能夠不用擔憂底層線程處理,經過對Task不一樣的配置,能寫出較高性能的多線程併發程序,而後探尋了.NET 4.5引入了的async await異步函數內部作了些啥,知道async await經過和TPL的配合,簡化了編寫異步編程的方式,特別適合I/O密集型的異步操做,本文只是起到對於Task和async await有個快速的理解做用,而關於微軟圍繞Task作的事情遠遠不止如此,例如經過ValueTask優化Task,還有更利於CPU密集型操做的TPL中的Parallel和PLINQ api等等,能夠參考其餘書籍或者msdn更深刻了解
Asynchronous programming patterns
Async in depth
ThreadPool 類
Understanding C# async / await 《CLR Via C# 第四版》 《Window核心編程第五版》