这里先阐释清“创建新线程”这个概念。我认为在这种情况下大家说的“创建新线程”可以被认为是与调用方法使用不同的线程,这个线程可能是线程池已有的,也可能是新建并被加入到线程池的线程。明确这给之后,继续说线程问题。
首先肯定一点async/await关键字不会创建新线程是对的。如上文代码中所示async/await被编译为一个状态机的确不参与Task的创建,实际新建Task的是被调用的异步方法。也就是说每调用一次异步方法(每一个await)都会产生一个新的Task,这个Task会自动执行。前面说过Task由TaskScheduler安排执行,一般都会在一个与调用线程不同的线程上执行。
为了把这个问题解释清楚,假设调用异步方法的线程为A,异步方法启动后在B线程执行。当B线程开始执行后,A线程将交出控制权。异步方法执行结束后,后续代码(await后面的代码)将在B线程上使用A线程的ExecutionContext(和SynchronizationContext,默认情况)继续执行。
注意这个A线程到B线程控制权的转换正是async异步模式的精髓之一。在WPF等这样的客户端环境这样做不会阻塞UI线程,使界面不失去响应。在MVC这样的Web环境可以及时释放HTTP线程,使Web服务器可以接收更多请求。毕竟B线程这种线程池中的线程成本更低。这样就是为什么既然也要花等待异步操作完成的时间,还要另外使用异步方法的原因 - 及时释放调用线程,让低成本的线程去处理耗时的任务。
最后当需要在发起执行的线程(这里是A线程)上继续进行处理时只要获得当时A线程的ExecutionContext和SynchronizationContext就可以了,并在这些Context完成剩余操作即可。
如果后续还有其他await,则会出现C线程,D线程等。如B调用了C的话,B的各种Context会被传递给C。当从异步方法返回后,执行的线程变了但是Context没变。这样异步方法给我们的感觉就像是同步一般。这也就是async/await方法的精妙之处。
那个Task的ConfigureAwait方法又是做什么用的呢,理解了上文就很好理解这个方法了。在异步方法返回时,会发生线程切换,默认情况下(ConfigureAwait(true)时)ExecutionContext和SynchronizationContext都会被传递。如果ConfigureAwait(false)则只有ExecutionContext会被传递,SynchronizationContext不会被传递。在WPF等客户端程序UI部分,应该使用默认设置让SynchronizationContext保持传递,这样异步代码的后续代码才能正常操作UI。除此之外的其他情况,如上面的Service类中,都该使用ConfigureAwait(false)以放弃SynchronizationContext的传递来提高性能。
下面以图应该会对上面这段文字有更深的了解:
吐槽一下,本来是想用vs生成的时序图进行演示呢。结果发现vs2015取消这个功能了。手头也没有其他版本的vs。就用代码截图来掩饰这个线程变化过程吧。
首先是控制台程序的线程变化情况:
图8
因为控制台应用没有SynchronizationContext,所以可以清楚的看到线程的变化。
下面看看在WPF中类似流程执行的样子:
图9
可以看到在默认情况下每个await后的异步代码返回到都回到UI线程,即所有await的后继代码都使用UI线程的SynchronizationContext来执行。除了调用方法外,其它所有的方法没有必要返回UI线程,所以我们应该把除调用开始处(即Button_Click方法)外的所有异步调用都配置为ConfigureAwait(false)。
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private async void Button_Click(object sender, RoutedEventArgs e) { var userService = new Service(); Debug.Write(Thread.CurrentThread.ManagedThreadId); var avatar = await userService.GetUserAvatarAsync(1); Debug.Write(Thread.CurrentThread.ManagedThreadId); //使用获取的avatar } } public class Service { private readonly Repository _repository; private readonly WebHepler _webHelpler; public Service() { _repository = new Repository(); _webHelpler = new WebHepler(); } public async Task<byte[]> GetUserAvatarAsync(int id) { var user = await _repository.GetByIdAsync(id).ConfigureAwait(false); var email = user.Email; var avatar = await _webHelpler.GetAvatarByEmailAsync(email).ConfigureAwait(false); return avatar; } } public class Repository { private readonly DbContext _dbContext; private readonly DbSet<User> _set; public Repository() { _dbContext = new DbContext(""); _set = _dbContext.Set<User>(); } public async Task<User> GetByIdAsync(int id) { //IO... var user = await _set.FindAsync(id).ConfigureAwait(false); return user; } } public class WebHepler { private readonly HttpClient _httpClient; public WebHepler() { _httpClient = new HttpClient(); } public async Task<byte[]> GetAvatarByEmailAsync(string email) { var url = "http://avater-service-sample/" + email; var resp = await _httpClient.GetByteArrayAsync(url); return resp; } }