当ASP.NET处理一个请求,一个线程从线程池分配和请求上下文被创建。请求上下文包含有关当前请求的信息,可以通过staticHttpContext.Current属性访问该信息。然后,将请求的请求上下文分配给处理该请求的线程。
给定的请求上下文一次只能在一个线程上处于活动状态。
当执行到达时await,在异步方法运行时,处理请求的线程将返回到线程池,并且请求上下文可以自由供另一个线程使用。
public async Task<ActionResult> Index() { // 在最初分配的线程上执行 var products = await dbContext.Products.ToListAsync(); // Execution resumes on a "random" thread from the pool // 执行将继续使用原始请求上下文。 return View(products); }
任务完成后,线程池将分配另一个线程以继续执行请求。然后将请求上下文分配给该线程。这可能是原始线程,也可能不是。
当的结果async方法调用等待同步死锁可能出现。例如,以下代码将在IndexSync()调用时导致死锁:
public async Task<ActionResult> Index() { // 在最初分配的线程上执行 List<Product> products = await dbContext.Products.ToListAsync(); // Execution resumes on a "random" thread from the pool return View(products); } public ActionResult IndexSync() { Task<ActionResult> task = Index(); // 阻止同步等待结果 ActionResult result = Task.Result; return result; }
这是因为在默认情况下,等待的任务在这种情况下将捕获上下文(在请求上下文的情况下),并在完成后尝试使用它。db.Products.ToListAsync()ASP.NET
当整个调用堆栈都是异步的时,就没有问题,因为一旦await到达原始线程,就释放了请求上下文。
当我们使用Task.Result或(或其他阻止方法)同步阻止时,原始线程仍处于活动状态并保留请求上下文。等待的方法仍然异步运行,并且一旦尝试运行回调,即一旦等待的任务返回,它就会尝试获取请求上下文。Task.Wait()
因此,出现死锁的原因是,当具有请求上下文的阻塞线程正在等待异步操作完成时,异步操作试图获取请求上下文以便完成。
默认情况下,对等待任务的调用将捕获当前上下文,并在完成后尝试在该上下文上恢复执行。
通过使用ConfigureAwait(false)它,可以防止死锁,并且可以避免死锁。
public async Task<ActionResult> Index() { // 在最初分配的线程上执行 List<Product> products = await dbContext.Products.ToListAsync().ConfigureAwait(false); // Execution resumes on a "random" thread from the pool without the original request context return View(products); } public ActionResult IndexSync() { Task<ActionResult> task = Index(); // 阻止同步等待结果 ActionResult result = Task.Result; return result; }
当需要阻塞异步代码时,这可以避免死锁,但这是以丢失连续上下文(调用等待后的代码)为代价的。
在ASP.NET这意味着,如果下面的调用你的代码试图从上下文的访问信息,例如则信息已经丢失。在这种情况下,为null。例如:await someTask.ConfigureAwait(false);HttpContext.Current.UserHttpContext.Current
public async Task<ActionResult> Index() { // 包含有关用户发送请求的信息 var user = System.Web.HttpContext.Current.User; using (var client = new HttpClient()) { await client.GetAsync("http://google.com").ConfigureAwait(false); } // 空引用异常,当前为空 var user2 = System.Web.HttpContext.Current.User; return View(); }
如果ConfigureAwait(true)使用(等于根本没有ConfigureAwait),则将两者user同时user2填充相同的数据。
因此,通常建议ConfigureAwait(false)在不再使用上下文的库代码中使用。