我必须在我公司的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++;
});
我考虑过使用lock
或mutex
,但它不会帮助我,因为他们只会等待后续执行。我需要一些解决方案,只在一个线程的代码中执行ONCE。可能吗?谁能分享一些亮点?
谢谢和亲切的问候, Leandro的
答案 0 :(得分:0)
您可以使用Double-checked locking模式。这通常用于单身人士,但你不是在这里制作一个单身人士,所以像Lazy<T>
这样的通用单身人士不适用。
它的工作原理如下:
将您的共享数据分成某种类:
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.
}
}
在您的外部课程中添加以下内容:
object padlock;
volatile QuerySharedData data
在线程的回调委托中,执行以下操作:
if (data == null)
{
lock (padlock)
{
if (data == null)
{
data = new QuerySharedData(); // this does all the work to initialize the shared fields
}
}
}
var localData = data
然后使用localData
中的共享查询数据通过将共享查询数据分组到从属类中,可以避免使其各个字段变得不稳定。
有关此volatile
的更多信息:Part 4: Advanced Threading。
更新我的假设是QuerySharedData
保留的所有类和字段在初始化后都是只读的。如果不是这样,例如,如果您初始化一个列表但在许多线程中添加它,则此模式将不适合您。您必须考虑使用Thread-Safe Collections等内容。
答案 1 :(得分:0)
正如您在评论中所述,您将在循环体之外声明变量。这就是你的竞争条件所源自的地方。
我们以变量listUserUpdate
为例。它由并行执行线程随机访问。虽然一个线程仍在添加,例如在listUserUpdate.Add(gObject);
另一个帖子中,可能已经在listUserUpdate = new List<RNObject>();
重置列表或在listUserUpdate.ToArray()
中枚举列表。
您确实需要将该代码重构为