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));

    }

发生的问题是:

  • 在上面的代码中,对于字典中的所有 Service 对象,都没有进行后续的方法调用,导致进一步处理出现问题。它必须以并行模式从字典中获取服务对象

  • db 字典包含所有 Ids /Service 对象,但我的理解是,在 Singleton 类的并行模式下处理时,很少有对象被跳过导致问题。

  • 据我了解,传递的服务 id 和创建的服务对象是线程本地的,所以不应该有我面临的问题。只有当给定方法调用一个线程用它替换另一个线程的服务 id 值时,这种问题才可能发生,因此两者都以 Service 对象结束,因此很少被跳过,这在我看来很奇怪,除非我这样做在这种情况下无法正确理解多线程

  • 目前,我可以通过使用 foreach 循环而不是 Parallel.ForEach / Parallel.Invoke 在非线程模式下运行相同的代码

请查看并让我知道您的观点或任何可以帮助我解决问题的指针

4

2 回答 2

1

据我了解,传递的服务 ID 和创建的服务对象是线程本地的

您的理解是不正确的,如果两个线程请求相同的服务 id,那么这两个线程将都在同一个单一对象上工作。如果您想要单独的对象,则需要进行某种new Service()调用GetServiceByIDFromDictionary而不是现有值的字典。

因为多个线程可能正在使用相同的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 在您的控制之下,并且您可以以某种方式修改service.InitLanes.Add为线程安全的(可能使用命名空间InitLanes中的线程安全集合进行更改System.Collections.Concurrent),这将是比锁定更好的解决方案。

于 2013-11-15T08:53:45.313 回答
1

1.实现单例总是考虑以多线程方式使用它。始终使用多线程单例模式变体,其中之一 - 惰性单例。使用带有适当LazyThreadSafeMode 构造函数参数的System.Lazy使用 Lazy 单例:

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;
         }
     }
}

在这里阅读

2.在并行循环体中使用你的服务变量的锁定

// 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.这里考虑使用多线程。使用锁定代码使您的代码不像同步那么高效。因此,请确保您的多线程/并行代码为您带来优势

4.使用适当的并发集合而不是重新发明轮子 - System.Collections.Concurrent Namespace

于 2013-11-15T13:45:22.283 回答