一旦你输入new Thread(),那就槽糕了,说明项目中的代码太过时了。
现代的异步.NET程序使用两个关键字:async和await。async关键字加在方法声明上,它的主要目的是使方法内的await关键字生效(为了保持向后兼容,同时引入了这两个关键字)。如果async方法有返回值,应返回Task<T>;如果没有返回值,应返回Task。这些task类型相当于future,用来在异步方法结束时通知主程序。
不要用void作为async方法的返回类型!async方法可以返回void,但是这仅限于编写事件处理程序。一个普通的async方法如果没有返回值,要返回Task,而不是void。
async Task DoSomethingAsync()
{
int val = 13;
//异步方式等待1秒
await Task.Delay(TimeSpan.FromSeconds(1));
val *= 2;
//异步方式等待1秒
await Task.Delay(TimeSpan.FromSeconds(1));
Trace.WriteLine(val);
}
和其他方法一样,async方法在开始时以同步方式执行。在async方法内部,await关键字对它的参数执行一个异步等待。它首先检查操作是否已经完成,如果完成了,就继续运行(同步方式)。否则,它会暂停async方法,并返回,留下一个未完成的task。一段时间后,操作完成,async方法就恢复运行。
一个async方法是由多个同步执行的程序块组成的,每个同步程序块之间由await语句分隔。第一个同步程序块在调用这个方法的线程中运行,但其他同步程序块在哪里运行呢?情况比较复杂。
最常见的情况是,用await语句等待一个任务完成,当该方法在await处暂停时,就可以捕捉上下文(context)。如果当前SynchronizationContext不为空,这个上下文就是当前SynchronizationContext。如果当前SynchronizationContext为空,则这个上下文为当前TaskScheduler。该方法会在这个上下文中继续运行。一般来说,运行UI线程时采用UI上下文,处理ASP.NET请求时采用ASP.NET请求上下文,其他很多情况下则采用线程池上下文。
因此,在上面的代码中,每个同步程序块会试图在原始的上下文中恢复运行。如果在UI线程中调用DoSomethingAsync,这个方法的每个同步程序块都将在此UI线程上运行。但是,如果在线程池线程中调用,每个同步程序块将在线程池线程上运行。
要避免这种错误行为,可以在await中使用ConfigureAwait方法,将参数continueOn CapturedContext设为false。接下来的代码刚开始会在调用的线程里运行,在被await暂停后,则会在线程池线程里继续运行:
async Task DoSomethingAsync()
{
int val = 13;
//异步方式等待1秒
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
val *= 2;
//异步方式等待1秒
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
Trace.WriteLine(val.ToString());
}