ConfigureAwait(false)是否触发ThreadPool新线程?为何HttpContext为空但类字段值不变
示例场景
我们基于以下.NET Framework MVC 应用代码进行分析:
public class HomeController : Controller { private int _someFiled = 0; public async Task<string> Get() { var context = System.Web.HttpContext.Current; _someFiled = 5; var jsonTask = await GetJsonAsync( new Uri("https://jsonplaceholder.typicode.com/todos")); return jsonTask; } public async Task<string> GetJsonAsync(Uri uri) { using (var client = new HttpClient()) { var jsonString = await client.GetStringAsync(uri).ConfigureAwait(false); var context = System.Web.HttpContext.Current; var value = _someFiled; return jsonString; } } }
已知逻辑:Get动作执行初期会将System.Web.HttpContext.Current赋值给context变量,同时将_someField赋值为5;GetJsonAsync中使用ConfigureAwait(false)后,后续延续操作不会在原上下文线程恢复。实际运行时,GetJsonAsync中读取的context为null,但_someField的值仍然为5。针对相关疑问解答如下:
问题1:ConfigureAwait(false)是否会触发来自ThreadPool的新线程?
答案是不一定,ConfigureAwait(false)的核心作用是告知异步等待操作不需要捕获并恢复到当前的SynchronizationContext,本身不会主动创建新线程:
- 如果await的异步操作已经同步完成(比如请求命中本地缓存直接返回结果),后续代码会直接在当前调用线程上同步执行,不会用到额外线程
- 只有当异步操作确实需要挂起等待时,等待结束后的延续代码才会被调度到ThreadPool线程执行,不会回到原请求的上下文线程
问题2:执行ConfigureAwait(false)语句后,为什么System.Web.HttpContext.Current为null,而_someField的值却和之前保持一致?
两个现象的本质是变量的存储位置和作用域不同:
为什么System.Web.HttpContext.Current为null
System.Web.HttpContext.Current是基于**线程本地存储(TLS)**实现的线程静态变量,每个线程的TLS空间相互独立,取值仅在当前线程内生效。.NET Framework MVC处理请求时,仅会将当前请求的HttpContext挂载到处理请求的原始线程的TLS中。当使用ConfigureAwait(false)不恢复原上下文时,延续代码运行在ThreadPool的其他线程上,新线程的TLS中没有存储当前请求的HttpContext,因此取值为null。
为什么_someField的值保持不变
_someField是HomeController类的实例成员,存储在托管堆中,和运行线程完全无关。请求处理完成前HomeController的实例不会被回收,所有访问该实例的线程读取的都是同一个实例的成员值。你在Get方法中已经将该实例的_someField赋值为5,后续无论哪个线程访问同一个实例的_someField,读到的都是修改后的值,和当前运行的线程没有关系。
内容的提问来源于stack exchange,提问作者west

