在Parallel.ForEach中正确执行一部分代码

时间:2014-07-23 21:29:37

标签: c# multithreading rightnow-crm

我必须在我公司的CRM解决方案(Oracle现在的最新版本)中查询我们的60万用户,如果它们存在则更新它们或在不存在的情况下创建它们。要知道用户是否已经存在,请使用第三方WS。由于每次获得响应所花费的时间(大约1秒),有60万用户这可能是一个真正的痛苦。所以我设法改变我的代码使用Parallel.ForEach,仅在0.35秒内查询每条记录,并将其添加到要创建或更新的List<User>条记录中(现在是有点愚蠢的)所以我需要将它们分成2个列表并调用2个不同的WS方法。)

我的代码用于在多线程之前完美运行,但耗时太长。问题是,当我尝试通过Web服务更新或创建时,我无法使批处理太大或者出现超时。所以我一次向他们发送大约500条记录,当它运行关键代码部分时,它会执行多次。

Parallel.ForEach(boDS.USERS.AsEnumerable(), new ParallelOptions { MaxDegreeOfParallelism = -1 }, row =>
{
    ...
    user = null;
    user = QueryUserById(row["USER_ID"].Trim());

    if (user == null)
    {
        isUpdate = false;
        gObject.ID = new ID();
    }
    else
    {
        isUpdate = true;
        gObject.ID = user.ID;
    }

    ... fill user attributes as generic fields ...

    gObject.GenericFields = listGenericFields.ToArray();

    if (isUpdate)
        listUserUpdate.Add(gObject);
    else
        listUserCreate.Add(gObject);

    if (i == batchSize - 1 || i == (boDS.USERS.Rows.Count - 1))
    {               
        UpdateProcessingOptions upo = new UpdateProcessingOptions();
        CreateProcessingOptions cpo = new CreateProcessingOptions();
        upo.SuppressExternalEvents = false;
        upo.SuppressRules = false;
        cpo.SuppressExternalEvents = false;
        cpo.SuppressRules = false;

        RNObject[] results = null;

        // <Critical_code>

        if (listUserCreate.Count > 0)
        {
            results = _service.Create(_clientInfoHeader, listUserCreate.ToArray(), cpo);
        }
        if (listUserUpdate.Count > 0)
        {
            _service.Update(_clientInfoHeader, listUserUpdate.ToArray(), upo);
        }
        // </Critical_code>

        listUserUpdate = new List<RNObject>();
        listUserCreate = new List<RNObject>();
    }
    i++;
});

我考虑过使用lockmutex,但它不会帮助我,因为他们只会等待后续执行。我需要一些解决方案,只在一个线程的代码中执行ONCE。可能吗?谁能分享一些亮点?

谢谢和亲切的问候, Leandro的

2 个答案:

答案 0 :(得分:0)

您可以使用Double-checked locking模式。这通常用于单身人士,但你不是在这里制作一个单身人士,所以像Lazy<T>这样的通用单身人士不适用。

它的工作原理如下:

  1. 将您的共享数据分成某种类:

    class QuerySharedData { // All the write-once-read-many fields that need to be shared between threads public QuerySharedData() { // Compute all the write-once-read-many fields. Or use a static Create method if that's handy. } }

  2. 在您的外部课程中添加以下内容:

    object padlock; volatile QuerySharedData data

  3. 在线程的回调委托中,执行以下操作:

    if (data == null) { lock (padlock) { if (data == null) { data = new QuerySharedData(); // this does all the work to initialize the shared fields } } } var localData = data

  4. 然后使用localData中的共享查询数据通过将共享查询数据分组到从属类中,可以避免使其各个字段变得不稳定。

    有关此volatile的更多信息:Part 4: Advanced Threading

    更新我的假设是QuerySharedData保留的所有类和字段在初始化后都是只读的。如果不是这样,例如,如果您初始化一个列表但在许多线程中添加它,则此模式将不适合您。您必须考虑使用Thread-Safe Collections等内容。

答案 1 :(得分:0)

正如您在评论中所述,您将在循环体之外声明变量。这就是你的竞争条件所源自的地方。

我们以变量listUserUpdate为例。它由并行执行线程随机访问。虽然一个线程仍在添加,例如在listUserUpdate.Add(gObject);另一个帖子中,可能已经在listUserUpdate = new List<RNObject>();重置列表或在listUserUpdate.ToArray()中枚举列表。

您确实需要将该代码重构为

  • 通过在循环体内移动变量和
  • ,使每个循环彼此独立运行
  • 使用锁和/或并发集合以同步方式访问数据