承接上文。web
线程池主要有两个好处:编程
总之,Windows系统自带了线程池的功能,一般状况下,你不可能有更好的实现。因此只需了解如何使用。安全
Windows的线程池有两种,分别是非托管线程池和托管线程池(即.NET线程池)。下面分别来介绍。多线程
其中的线程分为IO线程和工做者线程(或非IO线程)。架构
QueueUserWorkItem
会让一个监听在IOCP上的工做者线程醒来,并执行例程。BindIOCompletionCallback
把一个文件句柄绑定到线程池的IOCP上。当此文件有关的IO操做完成时,一个工做者线程会被唤醒来执行后面的操做。QueueUserWorkItem
并传入WT_EXECUTEINPERSISTENTTHREAD
标识时,会将一个APC回调放入IO线程的APC队列中。其中的线程分为工做者线程和IO线程。app
Task.Run()
或ThreadPool.QueueUserWorkItem()
来添加任务到任务队列中。ThreadPool.UnsafeQueueNativeOverlapped()
能够将任务添加到IO线程中。但不多使用。可见,托管与非托管线程池的差距是巨大的。异步
Thread
, ThreadPool
等接口。直接操做线程。Task
。async/await
。下面来着重分析Task编程模式。async
Task
最重要的两个方法是Task.Run()
和new Task().Start()
。使用这两个方法:异步编程
Task.Result
中。Task.Wait()
或Task.Result
会阻塞地等待工做线程执行结束。如下图示演示了这个过程具体经历了什么。函数
调用Task.ContinueWith(continueAction, continueOptions)
。其中,ContinuationOptions是一个枚举,提供了更多选项。
这个过程如何理解呢?请看下图。
如下是一些主要的属性:
它其实是Task.ContinueWith()
的语法糖。
为此咱们来看一段程序:
private async void btnDoStuff_Click() { lblStatus.Content = "Doing Stuff"; await Task.Delay(4000); lblStatus.Content = "After await"; }
它至关于
private void btnDoStuff_Click() { lblStatus.Content = "Doing Stuff"; Task t = Task.Delay(4000); t.ContinueWith(task => lblStatus.Content = "After await"); }
可是ContinueWith
里面的是一个委托,最好写成函数。另写一个函数最简单,不过C#使用另外一种“状态机”技术把这个函数写在了原来的方法内。也就是说,第一段代码实际上会被编译为(并不精确):
private void btnDoStuff_Click(int step) { switch (step) { case 0: lblStatus.Content = "Doing Stuff"; Task t = Task.Delay(4000); t.ContinueWith(task => btnDoStuff_Click(task.Step)); break; case 1: lblStatus.Content = "After await"; break; } }
方法被添加了一个参数step
, 第一次调用方法时step
为0,此后每进入一次则加一。由此则用一个方法实现了两端逻辑,这即是状态机重入技术。C#的另外一个语法糖:用yield
实现IEnumerable
接口也是采用这种技术。
它在多线程的比如空气:你能够不知道它,但它很是重要。ExecutionContext
是为了解决线程本地存储在多线程中没法传递的问题:总得有一种机制可以传递全局信息。不然只能经过函数调用参数传递了。
当一个线程发起异步调用的时候,ExecutionContext
会自动的在线程之间传递如下信息:
它是为了描述异步调用返回时的行为所建立的抽象。它有两个基本接口方法:
Send
同步地等待任务执行完毕。Post
把任务发出去就无论了。那么异步调用返回时的行为是什么意思?既然是抽象,那就会有具体的实现。后面咱们会看到几种实现。
当开始异步调用时,C#会捕获(capture)当前线程的同步上下文,并保存到Task中。在异步调用返回时,须要恢复(resume)同步上下文。此时就会调用同步上下文的Send
或者Post
。
下面是几种典型的同步上下文实现:
ASP.NET同步上下文。它有如下特色:
最后,调用ConfigureAwait(false)
时,就会跳过恢复同步上下文这一过程。因此,有时候必要(当不必传递任何信息时,使用它能够提升效率),有时候又会出错。例如,UI程序的异步调用原本没问题,你加了这个语句,反而会形成修改界面的操做可能不在UI线程中执行,从而出错。可是注意,不管如何,执行上下文都是会传递的。
结合以上,第一段程序的更精确的编译后版本是这样的:
private void btnDoStuff_Click(int step) { switch (step) { case 0: lblStatus.Content = "Doing Stuff"; Task t = Task.Delay(4000); t.ContinueWith( task => SynchronizationContext.Current.Post( state => btnDoStuff_Click(task.Step), task) ); break; case 1: lblStatus.Content = "After await"; break; } }
这个问题曾经困扰我好久。若是我当前的线程调用一个异步调用后返回了,那究竟是谁在完成真正调用的工做呢?答案是一个(或几个)共享的线程:线程池中的IO线程。
以下是一段代码:
async void GetButton_OnClick(object o, EventArgs e) { Task<Image> task = GetFaviconAsync(_url); Image image = await task; AddAFavicon(image); } async Task<Image> GetFaviconAsync(string url) { var task = _webClient.DownloadDataTaskAsync(url); byte[] bytes = await task; return MakeImage(bytes); }
线程的执行状况以下图:
大部分的时间都在用户线程中。只有调用到很是底层,IO完成以后,才有IO线程被唤醒(见11),而后它调用Task的同步上下文的Post
,将剩下的任务再交给用户线程去执行。
下面是一个动态的解释:
关于IIS的架构和工做过程,有一些资料,这里也不打算深究。提供两张图,以供理解。