EF数据上下文 - 异步/等待&多线程 [英] EF Data Context - Async/Await & Multithreading

查看:643
本文介绍了EF数据上下文 - 异步/等待&多线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常使用 async / await 来确保ASP.NET MVC Web API线程不被更长时间运行的I / O和网络操作(特别是数据库调用)阻止。

I frequently use async/await to ensure ASP.NET MVC Web API threads are not blocked by longer-running I/O and network operations, specifically database calls.

System.Data.Entity 命名空间在这里提供了各种帮助程序扩展,例如FirstOrDefaultAsync , ContainsAsync

The System.Data.Entity namespace provides a variety of helper extensions here, such as FirstOrDefaultAsync, ContainsAsync, CountAsync and so forth.

然而,由于数据上下文不是线程安全的,这意味着以下代码是有问题的:

However, since data contexts are not thread safe, this means that the following code is problematic:

var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);

其实我有时会看到例外:

In fact, I'm sometimes seeing exceptions such as:


System.InvalidOperationException:
连接未关闭。连接的当前状态是打开的。

System.InvalidOperationException: The connection was not closed. The connection's current state is open.

正确的模式是使用单独的使用(新的DbContext。 ..)块为每个异步调用数据库?那么只是执行同步呢?

Is the correct pattern then to use a separate using(new DbContext...) block for each asynchronous call to the database? Is it potentially more beneficial to just execute synchronous then instead?

推荐答案

这里有一个僵局。负责ASP.NET Web API执行环境的线程模型的AspNetSynchronizationContext AspNetSynchronizationContext 并不保证等待将发生在同一个线程上。整个想法是使ASP.NET应用程序更具可伸缩性,所以从 ThreadPool 中的线程数少于挂起的同步操作。

We have a stalemate situation here. AspNetSynchronizationContext, which is responsible for the threading model of an ASP.NET Web API execution environment, does not guarantee that asynchronous continuation after await will take place on the same thread. The whole idea of this is to make ASP.NET apps more scalable, so less threads from ThreadPool are blocked with pending synchronous operations.

但是, DataContext 不是线程安全的,所以不应该使用线程切换可能会跨越 DataContext API调用。

However, DataContext is not thread-safe, so it shouldn't be used where a thread switch may potentially occurr across DataContext API calls. A separate using construct per asynchronous call will not help, either:

var something;
using (var dataContext = new DataContext())
{
    something = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
}

这是因为 DataContext.Dispose 可能在与最初创建对象的线程不同的线程上执行,而这不是 DataContext 期望的。

That's because DataContext.Dispose might be executed on a different thread from the one the object was originally created on, and this is not something DataContext would expect.

如果您喜欢坚持使用 DataContext API,则将其强制同步似乎是唯一可行的选择。我不知道该声明是否应该扩展到整个EF API,但是我想使用 DataContext API创建的任何子对象也可能不是线程安全的。因此,在ASP.NET中,使用范围的应该限于两个相邻的 await 调用之间。

If you like to stick with the DataContext API, calling it synchronously appears to be the only feasible option. I'm not sure if that statement should be extended to the whole EF API, but I suppose any child objects created with DataContext API are probably not thread-safe, either. Thus, in ASP.NET their using scope should be limited to that of between two adjacent await calls.

可能很容易将一堆同步的 DataContext 调用卸载到具有的一个单独的线程await Task .Run(()=> {/ * do DataContext stuff here * /})。但是,这将是一个已知的反模式,特别是在ASP.NET的上下文中,它可能只会损害性能和可扩展性,因为它不会减少完成请求所需的线程数。

It might be tempting to offload a bunch of synchronous DataContext calls to a separate thread with await Task.Run(() => { /* do DataContext stuff here */ }). However, that'd be a known anti-pattern, especially in the context of ASP.NET where it might only hurt performance and scalability, as it would not reduce the number of threads required to fulfill the request.

不幸的是,虽然ASP.NET的异步体系结构非常好,但它仍然与一些已建立的API和模式不兼容(例如,这里是 a类似情况)。
这是特别难过的,因为我们不在这里处理并发API访问,即不会有一个线程同时尝试访问 DataContext 对象

Unfortunately, while the asynchronous architecture of ASP.NET is great, it remains being incompatible with some established APIs and patterns (e.g., here is a similar case). That's especially sad, because we're not dealing with concurrent API access here, i.e. no more than one thread is trying to access a DataContext object at the same time.

希望微软在未来版本的框架中解决这个问题。

Hopefully, Microsoft will address that in the future versions of the Framework.

[UPDATE ] 尽管如此,可能将EF逻辑卸载到单独的进程(作为WCF服务运行),这将为ASP.NET客户端逻辑提供线程安全的异步API。这样的过程可以使用自定义同步上下文作为事件机器进行编排,类似于Node.js.甚至可以运行一个像Node.js一样的公寓,每个公寓都保持对EF对象的线程关系。这将允许仍然受益于异步EF API。

[UPDATE] On a large scale though, it might be possible to offload the EF logic to a separate process (run as a WCF service) which would provide a thread-safe async API to the ASP.NET client logic. Such process can be orchestrated with a custom synchronization context as an event machine, similar to Node.js. It may even run a pool of Node.js-like apartments, each apartment maintaining the thread affinity for EF objects. That would allow to still benefit from the async EF API.

[更新] 这里是一些尝试找到解决这个问题的方法。

[UPDATE] Here is some attempt to find a solution to this problem.

这篇关于EF数据上下文 - 异步/等待&多线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆