控制器的非异步方法在使用或锁定时是否会阻止其他用户进入?

use*_*050 4 c# asynchronous actionmethod async-await asp.net-core

我有一个关于 ASP.Net Core 中控制器方法的新手问题。我有下面两种不同的实现,一种是同步的,另一种是异步的。

问题 - 我认为,我理解同步和异步之间的区别,但是当涉及到进入控制器方法时,如果另一个用户在其中并且仍在执行过程中,ASP.Net 是否会阻止所有用户进入同步方法跑过它吗?

如果是这样,如果我预计有数千个并发用户,那么这种同步方法会大大减慢我的应用程序的速度,对吧?

那么我应该在几乎所有地方都使用异步控制器调用吗?除非我特别想将另一个用户锁定在外面!

这里是异步的

    [HttpPost]
    public async Task<IActionResult> PostRecordAsync([FromBody] Record record)
    {
        record = await RecordsContext.PostRecordAsync(_context, record);
        return Ok(record);
    }

    public static async Task<Record> PostRecordAsync(MpidDbContext context, Record record)
    {
        context.Records.Add(record);
        await context.SaveChangesAsync();
        return record;
    }
Run Code Online (Sandbox Code Playgroud)

和非异步

    [HttpPost]
    public ActionResult PostRecord([FromBody] Record record)
    {
        record = RecordsContext.PostRecord(_context, record);
        return Ok(record);
    }

    public static Record PostRecord(MpidDbContext context, Record record)
    {
        context.Records.Add(record);
        context.SaveChanges();
        return record;
    }
Run Code Online (Sandbox Code Playgroud)

Ste*_* Py 5

不,同步调用不会阻塞。对于 Web 应用程序,异步方法将释放 Web 服务器线程以响应更多请求,而先前的调用正在等待来自资源或计算密集型操作的响应。

举个简单的例子。假设我有一个运行 100 个线程的 Web 服务器。这意味着它可以处理 100 个并发请求。由于要交给数据库进行一些读取和更新,每个请求最多需要 5 秒才能完成。

通过同步调用,如果有 500 个活动用户访问我的 Web 服务器,前 100 个用户将占用连接线程,而其余用户则等待线程。当操作完成时,下一个等待的请求将获得一个线程。请求在等待线程处理时可能会超时。

对于异步调用,如果有 500 个活动用户访问我的 Web 服务器,前 100 个用户将占用一个连接,但当等待操作并在数据库上运行时,Web 服务器线程将再次可用,开始处理另一个请求。当操作完成时,可以请求可用的 Web 服务器线程来处理结果并最终传回响应。Web 服务器通过接受请求并启动它们而无需等待先前的请求完成,以更灵敏的方式有效地处理更多并发请求。

异步确实会产生很小的性能成本,因为它需要能够在另一个线程上恢复执行,或者等待同步上下文。对于需要几秒钟才能运行的操作来说,这是非常值得的,但对于预计总是快速完成的方法来说,这会为所有调用增加少量但不必要的性能成本。作为一般规则,我默认使用同步调用,然后在我知道将有超过半秒左右的处理时间的情况下引入异步。(重要的数据库调用,例如ToList或复杂的查询、文件 I/O 等)

使用 ASP.Net Core 时,您还需要谨慎对待异步代码,因为它在没有同步上下文的情况下运行。在引用非线程安全代码(如 DbContext)时使用异步的代码可能会出现以下问题:发生多个异步调用而不依次等待每个异步调用。

IE

// Should be Ok...
var product = await context.Products.SingleAsync(x => x.ProductId == productId);
var customer = await context.Customers.SingleAsync(x => x.CustomerId == customerId);
var newOrder = new Order { Product = product, Customer = customer};

// Not Ok...
var productGet = context.Products.SingleAsync(x => x.ProductId == productId);
var customerGet = context.Customers.SingleAsync(x => x.CustomerId == customerId);
var newOrder = new Order { Product = await productGet, Customer = await customerGet};
Run Code Online (Sandbox Code Playgroud)

这不是最好的示例,但异步操作将在工作线程上执行,因此第二个示例将在不同线程上同时发生两个上下文操作。第一个示例将在单独的线程上运行每个操作,但第二个操作只有在第一个操作完成后才会运行。第二个将同时运行操作。除非您对相同表或相关表运行异步操作,否则它可能不会出现问题。我无法具体说明这样的代码的含义,但如果您在异步方法中遇到问题,则需要注意。

  • 线程行为将取决于同步上下文的存在和实现。我不能代表 EF 的 DBContext 实现,但如果您创建由第三个方法调用的 2 个异步方法并捕获每个方法中的当前线程 ID,您可以看到内部异步代码的线程 ID 彼此不同。在调用另一个之前等待一个仍然会是不同的线程 ID,但不会并行运行。最后等待两者将使两者并行运行。虽然数据库查询可以安全地跨线程运行,但针对本地缓存的检查和操作可能不会。 (3认同)