C#ASP.NET配置等待

示例

当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)在不再使用上下文的库代码中使用。