先看这一段异常信息:

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

不要被提示信息中的 Use 'await' 所迷惑,如果你仔细查看下代码,发现并没有什么问题,上面这段异常信息,是我们在 async/await 操作的时候经常遇到的,什么意思呢?我们分解下:

  • A second operation started on this context before a previous asynchronous operation completed. :在这个上下文,第二个操作开始于上一个异步操作完成之前。可能有点绕,简单说就是,在同一个上下文,一个异步操作还没完成,另一个操作就开始了。
  • Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. :在这个上下文,使用 await 来确保所有的异步操作完成于另一个方法调用之前。
  • Any instance members are not guaranteed to be thread safe.:所有实例成员都不能保证是线程安全的。

什么是线程安全呢?

  • 线程安全,指某个函数、函数库在多线程环境中被调用时,能够正确地处理各个线程的局部变量,使程序功能正确完成。(来自维基百科)

DbContext 是不是线程安全的呢?

  • The context is not thread safe. You can still create a multithreaded application as long as an instance of the same entity class is not tracked by multiple contexts at the same time.(来自 MSDN)

我们来解析这段话,首先,DbContext 不是线程安全的,也就是说,你在当前线程中,只能创建一个 DbContext 实例对象(特定情况下),并且这个对象并不能被共享,后面那句话是什么意思呢?注意其中的关键字,不被追踪的实体类,在同一时刻的多线程应用程序中,可以被多个上下文创建,不被追踪是什么意思呢?可以理解为不被修改的实体,通过这段代码获取:context.Entry(entity).State

我们知道 DbContext 就像一个大的数据容器,通过它,我们可以很方便的进行数据查询和修改,在之前的一篇博文中,有一段 EF DbContext SaveChanges 的源码:

[DebuggerStepThrough]
public virtual int SaveChanges(bool acceptAllChangesOnSuccess)
{
var entriesToSave = Entries
.Where(e => e.EntityState == EntityState.Added
|| e.EntityState == EntityState.Modified
|| e.EntityState == EntityState.Deleted)
.Select(e => e.PrepareToSave())
.ToList();
if (!entriesToSave.Any())
{
return 0;
}
try
{
var result = SaveChanges(entriesToSave);
if (acceptAllChangesOnSuccess)
{
AcceptAllChanges(entriesToSave);
}
return result;
}
catch
{
foreach (var entry in entriesToSave)
{
entry.AutoRollbackSidecars();
}
throw;
}
}

在 DbContext 执行 AcceptAllChanges 之前,会检测实体状态的改变,所以,SaveChanges 会和当前上下文一一对应,如果是同步方法,所有的操作都是等待,这是没有什么问题的,但试想一下,如果是异步多线程,当一个线程创建 DbContext 对象,然后进行一些实体状态修改,在还没有 AcceptAllChanges 执行之前,另一个线程也进行了同样的操作,虽然第一个线程可以 SaveChanges 成功,但是第二个线程肯定会报错,因为实体状态已经被另外一个线程中的 DbContext 应用了。

在多线程调用时,能够正确地处理各个线程的局部变量,使程序功能正确完成,这是线程安全,但显然 DbContext 并不能保证它一定能正确完成,所以它不是线程安全,MSDN 中的说法:Any public static members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

下面我们做一个测试,测试代码:

using (var context = new TestDbContext2())
{
var clients = await context.Clients.ToListAsync();
var servers = await context.Servers.ToListAsync();
}

上面代码是我们常写的,一个 DbContext 下可能有很多的操作,测试结果也没什么问题,我们接着再修改下代码:

using (var context = new TestDbContext2())
{
var clients = context.Clients.ToListAsync();
var servers = context.Servers.ToListAsync();
await Task.WhenAll(clients, servers);
}

Task.WhenAll 的意思是将所有等待的异步操作同时执行,执行后你会发现,会时不时的报一开始的那个错误,为什么这样会报错?并且还是时不时的呢?我们先分析下上面两段代码,有什么不同,其实都是异步,只是下面的同时执行异步方法,但并不是绝对同时,所以会时不时的报错,根据一开始对 DbContext 的分析,和上面的测试,我们就明白了:同一时刻,一个上下文只能执行一个异步方法,第一种写法其实也会报错的,但几率非常非常小,可以忽略不计,第二种写法我们只是把这种几率提高了,但也并不是绝对。

还有一种情况是,如果项目比较复杂,我们会一般会设计基于 DbContext 的 UnitOfWork,然后在项目开始的时候,进行 IoC 注入映射类型,比如下面这段代码:

UnityContainer container = new UnityContainer();
container.RegisterType<IUnitOfWork, UnitOfWork>(new PerResolveLifetimeManager());

除了映射类型之外,我们还会对 UnitOfWork 对象的生命周期进行管理,PerResolveLifetimeManager 的意思是每次请求进行解析对象,也就是说每次请求下,UnitOfWork 是唯一的,只是针对当前请求,为什么要这样设计?一方面为了共享 IUnitOfWork 对象的注入,比如 Application 中会对多个 Repository 进行操作,但现在我觉得,还有一个好处是减少线程安全错误几率的出现,因为之前说过,多线程情况下,一个线程创建 DbContext,然后进行修改实体状态,在应用更改之前,另一个线程同时创建了 DbContext,并也修改了实体状态,这时候,第一个线程创建的 DbContext 应用更改了,第二个线程创建的 DbContext 应用更改就会报错,所以,一个解决方法就是,减少 DbContext 的创建,比如,上面一个请求只创建一个 DbContext。

因为 DbContext 不是线程安全的,所以我们在多线程应用程序运用它的时候,要注意下面两点:

  • 同一时刻,一个上下文只能执行一个异步方法。
  • 实体状态改变,对应一个上下文,不能跨上下文修改实体状态,也不能跨上下文应用实体状态。

异步下使用 DbContext,我个人觉得,不管代码怎么写,还是会报线程安全的错误,只不过这种几率会很小很小,可能应用程序运行了几年,也不会出现一次错误,但出错几率会随着垃圾代码和高并发,慢慢会提高上来。

参考资料:

EntityFramework DbContext 线程安全的更多相关文章

  1. .net EntityFramework dbContext 如何实例化

    1 .DbContext怎么在Asp.mvc中使如何实例化 public class Repository { //实例化EF容器:有弊端.一个线程里可能会创建多个DbContext //DbCont ...

  2. EntityFramework DBContext 类动态控制 数据库连接(支持Oracle,SQL server)

     public class ManagementDBContext : DbContext     {         public static string configString = Conf ...

  3. MVC UnitOfWork EntityFramework架构

    MVC UnitOfWork EntityFramework架构,网站速度慢的原因总结! 最近参考使用了郭明峰的一套架构来做新的项目架构,这套架构看起来还是不错的,先向小郭同学的分享精神致敬! (郭同 ...

  4. 【EntityFramework 6.1.3】个人理解与问题记录

    前言 又是一个炎热夏日的晚上,开着空调听着音乐又开始了我们今天的博文.此文并不是ROM工具哪家强之类的引战贴,只是本文自己的一点看法和见解,望前辈看官有望斧正 声明 本文欢迎转载,原文地址:http: ...

  5. 关于EFCore线程内唯一

    EntityFramework的线程内唯一 EntityFramework的线程内唯一是通过httpcontext来实现的 public static DbContext DbContext() { ...

  6. EntityFramework 基础的crud

    EntityFramework 基础的crud操作 根据上一张实体映射的demo学习基础的crud操作 1.增加 BlogDbContext dbContext = new BlogDbContext ...

  7. 002从零开始入门Entity Framework Core——DbContext生存期、配置和初始化

    阅读须知:本文为入门介绍.指引文章,所示代码皆为最简易(或仅为实现功能)的演示示例版本,不一定切实符合个人(企业)实际开发需求. 一.DbContext生存期 DbContext 的生存期从创建实例时 ...

  8. MVC+MEF+UnitOfWork+EF架构,网站速度慢的原因总结!(附加ANTS Memory Profiler简单用法)

    (最近使用内存分析工具ANTS Memory Profiler,以及其他网友提供的意见发现最终导致内存泄漏的就是MEF,在此特地更新下,与大家分享!最下面红色字体) 最近参考使用了郭明峰的一套架构来做 ...

  9. EntityFrameworkCore之工作单元的封装

    1. 简介 2. DbContext 生命周期和使用规范 2.1. 生命周期 2.2. 使用规范 2.3. 避免 DbContext 线程处理问题 3. 封装-工作单元 3.1. 分析 3.2. 设计 ...

随机推荐

  1. Maven打包 报 Unable to locate the Javac Compiler in: C:\Program Files\Java\jre1.8.0_73\..\lib\tools.jar

    无法找到javac 编译环境 右键项目 --> properties -->Java Build Path -->选中JRE 点击右侧 Edit 编辑 --> 把你设置的JRE ...

  2. (转)Vsdocman7.2 注册版

    Vsdocman是一个优秀的.NET源代码注释编写工具,方便的以GUI的方式设计.NET源代码的注释.我们只是大自然的搬运工:http://download.csdn.net/detail/iamyg ...

  3. Permission denied user=hadoop access=WRITE inode=root rootsupergroup rwxr

    有段时间没有写了,反正我写的都是跟流水账一样.不为别人看,当然,其中也记录了很多我踩过的坑,可能也能给别人提个醒.最重要的是:这是我学习的记忆 上面的错误是由于我将reducer的输出目录设置在hdf ...

  4. 反向输出及sort排序

    建立条件:#include "algorithm"引用这个头文件 1.reverse 的用法,反向排序,由自己输入5个数: 1 2 3 4 5 for (int i = 0; i ...

  5. Sort简单排序

    List<T> 类型排序: public List<ProductionMaterialModel> OrderBybom(List<ProductionMaterial ...

  6. 字符串混淆技术应用 设计一个字符串混淆程序 可混淆.NET程序集中的字符串

    关于字符串的研究,目前已经有两篇. 原理篇:字符串混淆技术在.NET程序保护中的应用及如何解密被混淆的字符串  实践篇:字符串反混淆实战 Dotfuscator 4.9 字符串加密技术应对策略 今天来 ...

  7. 浅谈Android应用保护(一):Android应用逆向的基本方法

    对于未进行保护的Android应用,有很多方法和思路对其进行逆向分析和攻击.使用一些基本的方法,就可以打破对应用安全非常重要的机密性和完整性,实现获取其内部代码.数据,修改其代码逻辑和机制等操作.这篇 ...

  8. 求两点之间最短路径-Dijkstra算法

     Dijkstra算法 1.定义概览 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.D ...

  9. VBA批量查找和复制文件

    Function findAndCopy(srcFile As String, destFile As String, cmdFile As String) Dim WSH As Object, wE ...

  10. OpenGL快问快答

    OpenGL快问快答 本文内容主要来自对(http://www.opengl.org/wiki/FAQ)的翻译,随机加入了本人的观点.与原文相比,章节未必完整,含义未必雷同,顺序未必一致.仅供参考. ...