EntityFrameworkDbContext執行緒安全
不要被提示資訊中的 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,我個人覺得,不管程式碼怎麼寫,還是會報執行緒安全的錯誤,只不過這種機率會很小很小,可能應用程式執行了幾年,也不會出現一次錯誤,但出錯機率會隨著垃圾程式碼和高併發,慢慢會提高上來。
本文轉自田園裡的蟋蟀部落格園部落格,原文連結:http://www.cnblogs.com/xishuai/p/ef-dbcontext-thread-safe.html,如需轉載請自行聯絡原作者
相關文章
- 執行緒3--執行緒安全執行緒
- 執行緒安全和執行緒不安全理解執行緒
- 執行緒安全執行緒
- 多執行緒系列之 執行緒安全執行緒
- iOS 多執行緒之執行緒安全iOS執行緒
- iOS多執行緒之執行緒安全iOS執行緒
- Java執行緒(一):執行緒安全與不安全Java執行緒
- 【多執行緒總結(二)-執行緒安全與執行緒同步】執行緒
- 什麼是執行緒安全和執行緒不安全執行緒
- 【Java多執行緒】執行緒安全的集合Java執行緒
- 執行緒安全(二)執行緒
- Java執行緒安全Java執行緒
- Java - 執行緒安全Java執行緒
- Java 多執行緒基礎(四)執行緒安全Java執行緒
- iOS多執行緒安全-13種執行緒鎖?iOS執行緒
- 多執行緒,你覺得你安全了?(執行緒安全問題)執行緒
- PHP的執行緒安全與非執行緒安全版本的區別PHP執行緒
- 執行緒安全性執行緒
- 多執行緒安全(一)執行緒
- 執行緒安全操作HashMap執行緒HashMap
- java執行緒安全LockJava執行緒
- strerror執行緒安全分析Error執行緒
- 容器不是執行緒安全執行緒
- 併發程式設計之多執行緒執行緒安全程式設計執行緒
- 小度分享-【多執行緒工作及執行緒安全】執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- iOS開發基礎——執行緒安全(執行緒鎖)iOS執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- 什麼時候執行緒不安全?怎樣做到執行緒安全?怎麼擴充套件執行緒安全的類?執行緒套件
- [短文速讀 -5] 多執行緒程式設計引子:程式、執行緒、執行緒安全執行緒程式設計
- Java併發實戰一:執行緒與執行緒安全Java執行緒
- Java多執行緒中執行緒安全與鎖問題Java執行緒
- day20_多執行緒入門丶執行緒安全執行緒
- 多執行緒-以前的執行緒安全的類回顧執行緒
- 詳解Java執行緒安全Java執行緒
- Concurrency(四:執行緒安全)執行緒
- Java 執行緒安全 與 鎖Java執行緒
- .Net 執行緒安全集合執行緒