await and LINQ within DBContext

本文关键字:DBContext within LINQ and await | 更新日期: 2023-09-27 18:09:50

我们的应用程序中有一个服务层,它由三个逻辑层组成——web服务、业务模型服务(我们对执行业务逻辑和编排对各种存储库调用的层的名称),以及使用EF6连接到各种数据库的存储库层。

我们的许多存储库调用只是通过ToListAsync, FirstOrDefaultAsync直接从数据库集获取数据,像这样:

public async Task<MyObject> GetSomeData()
{
    using(var context = new myDBContext())
    {
        return await context.SomeDbSet.FirstOrDefault(o=>o.Something == true);  
    }
}

关于在这里使用await是否正确,我们有一些内部争论,因为在await之后,这个方法中没有任何执行。我/我们理解代码的编写方式,这是必要的,否则一旦方法存在,上下文就会被处理,这将导致异常。但是,如果我们在这里等待,我们必须一直等待调用堆栈向上(或向下,取决于您如何看待它),这将导致大量昂贵且有些不必要的上下文切换。

这里的另一个选项是使存储库方法同步,并在调用存储库方法的方法中执行Task.Run(),如:
Task.Run(() => MyRepository.GetSomeData());
如果需要的话,我们可以等待这个调用,或者只是将任务对象再次返回给调用者。这里的缺点是对数据库的调用变成同步的,并且池中的一个线程阻塞了整个数据库调用的长度。

所以这归结为哪个更贵?不必要的上下文切换通过等待或线程阻塞?似乎没有正确的答案,但是有最佳实践吗?

任何想法都会很感激。

await and LINQ within DBContext

当然,您应该使用async版本。

正如你所说的,如果你不await,你将在操作完成之前处理上下文,但这并不意味着调用方法也需要使用async-await。它们可以像您在Task.Run选项中提到的那样返回任务:

public Task<MyObject> FooAsync()
{
    // do some stuff
    return GetSomeDataAsync();
}
public async Task<MyObject> GetSomeDataAsync()
{
    using(var context = new myDBContext())
    {
        return await context.SomeDbSet.FirstOrDefault(o=>o.Something == true);  
    }
}

您提到在这种情况下的成本是一些昂贵的上下文切换。我不确定你是什么意思,但如果你指的是线程上下文切换,那么只有一个。在等待异步操作时,调用线程将被释放,当该操作完成时,另一个线程将继续运行。

与执行实际操作所需的时间相比,这不仅可以忽略不计,如果您使用Task.Run,那么当阻塞的线程从CPU中取出时,您将具有相同的上下文切换。

在同步操作中使用Task.Run是多余的。它只是阻塞了一个线程,它可能需要比异步等效的更多的上下文切换。

.NET Framework中有很多种" context ": LogicalCallContext, SynchronizationContext, HostExecutionContext, SecurityContext, ExecutionContext等。SynchronizationContext在使用Async/Await时被捕获,但它不是唯一被捕获的上下文。与SynchronizationContext一起,ExecutionContext也被捕获。ExecutionContext包括SecurityContext, LogicalCallContext等。

异步代码总是针对捕获的ExecutionContext执行。当一个await完成时,如果有一个当前的SynchronizationContext被捕获,那么表示异步方法剩余部分的continuation将被发布到该SynchronizationContext。

所以在Task下执行代码时。运行时,只有SynchronizationContext不会被捕获,但是ExecutionContext在任何情况下仍然会被捕获。在等待时,您可以使用ConfigureAwait(false)获得async/await捕获的未获取SynchronizationContext的相同行为。缺点是,当await完成时,SynchronizationContext将被忽略,并且框架将尝试在之前的异步操作完成的地方继续执行,这正是您想要的。

所以在你的场景中,我认为你应该使用async/await与ConfigureAwait(false),因为在这种情况下不会有任何SynchronizationContext的开销,同时也不会有阻塞任何线程的开销。

下面的帖子可能有助于获得更多的见解:https://msdn.microsoft.com/en-us/magazine/hh456402.aspx