2013-11-15 16 views
3

碼詳情:並行化/多線程一個單獨的對象方法調用

// Singleton class CollectionObject 

public class CollectionObject 
{ 
    private static CollectionObject instance = null;   

// GetInstance() is not called from multiple threads 
    public static CollectionObject GetInstance() 
    { 
     if (CollectionObject.instance == null) 
      CollectionObject.instance = new CollectionObject(); 

     return CollectionObject.instance; 
    } 

// Dictionary object contains Service ID (int) as key and Service object as the value 
// Dictionary is filled up during initiation, before the method call ReadServiceMatrix detailed underneath 

    public Dictionary<int, Service> serviceCollectionDictionary = new Dictionary<int,Service>(); 

    public Service GetServiceByIDFromDictionary(int servID) 
    { 
     if (this.serviceCollectionDictionary.ContainsKey(servID)) 
      return this.serviceCollectionDictionary[servID]; 
     else 
      return null; 
    } 

} 

DataTable serviceMatrix = new DataTable(); 

// Fill serviceMatrix data table from the database 

private int ReadServiceMatrix() 
{ 
    // Access the Singleton class object 
    CollectionObject collectionObject = CollectionObject.GetInstance(); 

    // Parallel processing of the data table rows 
    Parallel.ForEach<DataRow>(serviceMatrix.AsEnumerable(), row => 
    { 
     //Access Service ID from the Data table 
     string servIDStr = row["ServID"].ToString().Trim(); 

     // Access other column details for each row of the data table 

     string currLocIDStr = row["CurrLocId"].ToString().Trim(); 
     string CurrLocLoadFlagStr = row["CurrLocLoadFlag"].ToString().Trim(); 
     string nextLocIDStr = row["NextLocId"].ToString().Trim(); 
     string nextLocBreakFlagStr = row["NextLocBreakFlag"].ToString().Trim(); 
     string seqStr = row["Seq"].ToString().Trim(); 

     int servID = Int32.Parse(servIDStr); 
     int currLocID = Int32.Parse(currLocIDStr); 
     int nextLocID = Int32.Parse(nextLocIDStr); 
     bool nextLocBreakFlag = Int32.Parse(nextLocBreakFlagStr) > 0 ? true : false; 
     bool currLocBreakFlag = Int32.Parse(CurrLocLoadFlagStr) > 0 ? true : false; 
     int seq = Int32.Parse(seqStr); 

     // Method call leading to the issue (definition in Collection Object class) 
     // Fetch service object using the Service ID from the DB      

     Service service = collectionObject.GetServiceByIDFromDictionary(servID); 

     // Call a Service class method 

     service.InitLanes.Add(new Service.LaneNode(currLoc.SequentialID, currLocBreakFlag, nextLoc.SequentialID, nextLocBreakFlag, seq)); 

    } 

問題發生的:

  • 在上面的代碼在字典中的所有服務對象,後續方法調用沒有進行,導致進一步處理中的問題。它必須以並行方式從字典中提取服務對象

  • db一個字典包含所有的Ids /服務對象,但我的理解是在並行模式下處理Singleton類時,幾個對象被跳過導致這個問題。

  • 在我的理解中,傳遞的服務ID和創建的服務對象都是線程本地的,所以不應該存在我面臨的問題。這種問題是唯一可能的,當一個給定的方法調用一個線程替換另一個線程的服務id值,因此最終會得到Service對象,並且少數被跳過,這在我看來是奇怪的,直到和除非我做不知道在這種情況下正確

  • 目前我能夠通過使用foreach循環,而不是Parallel.ForEach/Parallel.Invoke

請參閱運行非線程模式相同的代碼多線程並讓我知道你的觀點或任何可以幫助我解決問題的指針

回答

0

在我的理解中的服務ID傳遞和服務對象創建 是線程局部

你的理解是不正確,如果兩個線程請求相同服務ID的兩個線程將是相同的奇異物體上兩個工作。如果你想要單獨的對象,你需要在GetServiceByIDFromDictionary中調用某種new Service()而不是現有值的字典。

因爲多個線程可能使用相同的service對象我認爲你的問題在於service.InitLanes.Add可能不是線程安全的事實。

最簡單的解決方法是隻鎖定該單步

//...SNIP... 

    Service service = collectionObject.GetServiceByIDFromDictionary(servID); 

    // Call a Service class method, only let one thread do it for this specific service instance, 
    // other threads locking on other instances will not block, only other threads using the same instance will block 
    lock(service) 
    { 
     service.InitLanes.Add(new Service.LaneNode(currLoc.SequentialID, currLocBreakFlag, nextLoc.SequentialID, nextLocBreakFlag, seq)); 
    } 

} 

這是假定這Parallel.Foreach是唯一的位置collectionObject.GetServiceByIDFromDictionary被同時使用。如果不是,則任何其他可能調用返回服務方法的位置也必須鎖定service

但是,如果服務是你的控制之下,你能以某種方式修改service.InitLanes.Add是線程安全的(也許是改變InitLanes出從System.Collections.Concurrent命名空間的線程安全的集合),這將是比鎖定一個更好的解決方案。

+0

感謝斯科特,這的確是問題,我並沒有意識到,多個線程越來越相同的服務對象,從而導致問題,InitLanes是一種自定義數據結構,它以非線程模式廣泛訪問,將它轉換爲線程安全列表是否明智,是否會影響非線程訪問。目前正如建議我使用鎖對象 –

+0

由於'InitLanes'是一個自定義數據結構,您只需要通過該類,並確保它正確鎖定以防止內部狀態被混淆。 ['ReaderWriterLock'](http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock%28v=vs.110%29.aspx)在這個轉換中可能會有很大的幫助。它將允許許多讀者同時訪問課程,但只要有一個作者阻止了訪問,這樣就不會有讀者或作者訪問受保護區域,而鎖定位置不在鎖定位置。 –

1

1.執行單例總是考慮以mulithreaded方式使用它。總是使用多線程單身模式變體,其中之一 - 懶惰單身人士。使用單懶使用System.Lazy適當LazyThreadSafeMode consturctor說法:

public class LazySingleton3 
{ 
    // static holder for instance, need to use lambda to enter code here 
    //construct since constructor private 
    private static readonly Lazy<LazySingleton3> _instance 
     = new Lazy<LazySingleton3>(() => new LazySingleton3(), 
              LazyThreadSafeMode.PublicationOnly); 

    // private to prevent direct instantiation. 
    private LazySingleton3() 
    { 
    } 

    // accessor for instance 
    public static LazySingleton3 Instance 
    { 
     get 
     { 
      return _instance.Value; 
     } 
    } 
} 

讀到它here

2.使用您的服務變量的並行循環體鎖ING

// Method call leading to the issue (definition in Collection Object class) 
// Fetch service object using the Service ID from the DB      
Service service = collectionObject.GetServiceByIDFromDictionary(servID); 

lock (service) 
{  
    // Call a Service class method   
    service.InitLanes.Add(new Service.LaneNode(currLoc.SequentialID, 
          currLocBreakFlag, nextLoc.SequentialID, 
          nextLocBreakFlag, seq)); 
} 

3.Consider在這裏使用多線程。使用鎖定代碼使您的代碼不像同步一樣完美。所以一定要多線程/ paralelised代碼爲您提供了優勢

4.使用適當的併發集合,而不是重新發明輪子 - System.Collections.Concurrent Namespace

相關問題