2010-08-25 28 views
3

我的代碼是用C#編寫的,數據層使用LINQ to SQL來填充/加載分離的對象類。LINQ to SQL DAL,這是線程安全嗎?

我最近更改了代碼以使用多線程,並且我非常確定我的DAL不是線程安全的。

你能告訴我,如果PopCall()和Count()是線程安全的,如果不是我如何解決它們?

public class DAL 
{ 
    //read one Call item from database and delete same item from database. 
    public static OCall PopCall() 
    { 
     using (var db = new MyDataContext()) 
     { 
      var fc = (from c in db.Calls where c.Called == false select c).FirstOrDefault(); 
      OCall call = FillOCall(fc); 
      if (fc != null) 
      { 
       db.Calls.DeleteOnSubmit(fc); 
       db.SubmitChanges(); 
      } 
      return call; 
     } 
    } 

    public static int Count() 
    { 
     using (var db = new MyDataContext()) 
     { 
      return (from c in db.Calls select c.ID).Count(); 
     } 
    } 

    private static OCall FillOCall(Model.Call c) 
    { 
     if (c != null) 
      return new OCall { ID = c.ID, Caller = c.Caller, Called = c.Called }; 
     else return null; 
    } 
} 

獨立式OCall類:

public class OCall 
{ 
    public int ID { get; set; } 
    public string Caller { get; set; } 
    public bool Called { get; set; } 
} 

回答

3

單獨它們是線程安全的,因爲它們使用獨立的數據上下文等。但是,它們不是原子單元。所以它是不是安全檢查計數是> 0,然後假設仍然有東西彈出。任何其他線程都可能會突變數據庫。

如果你需要這樣的事情,你可以在一個TransactionScope包裹,這將給你(默認),串行隔離級別:

using(var tran = new TransactionScope()) { 
    int count = OCall.Count(); 
    if(count > 0) { 
     var call = Count.PopCall(); 
     // TODO: something will call, assuming it is non-null 
    } 
} 

當然,這會產生堵塞。最好簡單地檢查FirstOrDefault()

請注意,PopCall仍然可能會拋出異常 - 如果另一個線程/進程刪除您獲取它之間的數據並調用SubmitChanges。關於它扔在這裏的好處是,你不應該找到你兩次返回相同的記錄。

SubmitChanges是事務性的,但讀取不是,除非事務範圍或類似事物跨越。爲了使PopCall原子不拋出:

public static OCall PopCall() 
{ 
    using(var tran = new TrasactionScope()) 
     using (var db = new MyDataContext()) 
     { 
      var fc = (from c in db.Calls where c.Called == false select c).FirstOrDefault(); 

      OCall call = FillOCall(fc); 

      if (fc != null) 
      { 
       db.Calls.DeleteOnSubmit(fc); 
       db.SubmitChanges(); 
      } 

      return call; 
     } 
     tran.Complete(); 
    } 
} 

現在FirstOrDefault由串行隔離級別的覆蓋,這樣做的讀取將在數據上的鎖。如果我們可以在這裏明確地發出UPDLOCK,但是LINQ-to-SQL不提供這個,那將會更好

+0

謝謝! ,似乎你使用TrasactionScope,因爲它是一個鎖定語句,我認爲TrasactionScope只能確保「全部或全部」SQL查詢執行。它是否也阻止其他線程? – RuSh 2010-08-25 11:43:23

+0

@sharru - 數據庫在可序列化隔離時執行該操作。如前所述,UPDLOCK會更好地避免更多的時間邊緣情況。 – 2010-08-25 17:17:38

1

COUNT()是線程安全的。同時調用兩次,來自兩個不同的線程不會損害任何內容。現在,另一個線程可能會在通話期間更改項目的數量,但那又如何?另一個線程在返回後可能會在一微秒內更改它的數量,並且您無能爲力。

另一方面,PopCall確實存在線程問題的可能性。一個線程可以讀取fc,然後在到達SubmitChanges()之前,另一個線程可能會介入並執行讀取&刪除,然後返回到第一個線程,該線程將嘗試刪除已刪除的記錄。然後,這兩個調用將返回相同的對象,即使您的意圖是一行只返回一次。

+0

其中一個'SubmitChanges'將在這種情況下拋出一個異常(有一個自動事務和行檢查) - 所以AFAIK你不應該得到並行返回的相同記錄。仍然是一個小故障,但不是這裏描述的小故障... – 2010-08-25 11:39:13

+0

謝謝! ,所以PopCall不會線程安全,我真的不想要兩次獲取記錄,哪些更改會使線程安全? – RuSh 2010-08-25 11:46:33

1

不幸的是,沒有任何數量的Linq-to-Sql欺騙,也沒有SqlClient隔離級別,也沒有System.Transactions可以使PopCall()線程安全,其中'線程安全'真正意味着'併發安全'(即當併發發生數據庫服務器,在客戶端代碼/進程的控制和範圍之外)。並且任何類型的C#鎖定和同步都不會幫助你。您只需深入瞭解關係型存儲引擎的工作原理,以便正確地獲取此文件。使用表格作爲隊列(就像你在這裏做的那樣)是非常棘手的,容易出現死鎖,並且很難使其正確。

即使不那麼幸運,您的解決方案也必須是平臺特定的。我只是要解釋用SQL Server來實現它的正確方法,那就是利用OUTPUT子句。如果您想了解更多詳細信息,請閱讀此文章Using tables as Queues。您的POP操作必須原子在數據庫中像這樣的調用來實現:

WITH cte AS (
SELECT TOP(1) ... 
FROM Calls WITH (READPAST) 
WHERE Called = 0) 
DELETE 
FROM cte 
OUTPUT DELETED.*; 

不僅如此,但Calls表必須被組織了一個最左邊的集羣上Called列鍵。爲什麼會出現這種情況,在我之前引用的文章中再次解釋。

在這種情況下,Count調用基本上是無用的。要正確檢查可用項目的唯一方法是Pop,要求Count只會對數據庫施加無用的壓力,以返回COUNT()值,這在併發環境下意味着什麼。