我会在下面提出问题,这些问题以某种方式相互关联并与下面的代码相关(简化 - 请参阅评论):
public class DataProcessor
{
private DataUpdater dataUpdater = new DataUpdater(someConnection);
public void Process()
{
Parallel.ForEach(itemCollection, (item, loopState) =>
{
ProcessItem(item);
// Q1. is this thread safe? (as I understand, same DataUpdater instance for all threads)
// Q4. should a new DataUpdater be created here and passed to ProcessItem as parameter instead?
// (commented as not relevant to question)
// if (mustStop())
// {
// loopState.Break();
// }
});
}
private void ProcessItem(Item item)
{
// some processing...
dataUpdater.Update(item); // Q2. should I lock here if 1. is not thread-safe?
}
}
public class DataUpdater // Q3. could/should this be made a static helper class?
{
private IDatabaseConnection databaseConnection;
public DataUpdater(IDatabaseConnection databaseConnection)
{
this.databaseConnection = databaseConnection;
}
public void Update(item)
{
using (var ctx = new DataContext(databaseConnection.ConnectionString))
{
// Linq2Sql
ctx.Item.InsertOnSubmit(item);
ctx.SubmitChanges();
}
}
}
在上面的代码中,数据处理器使用Parallel.Foreach
循环来加速ProcessItem
方法中的一些繁重处理(与问题无关)。处理完成后,该项目将通过DataUpdater类在数据库中更新。
问题是:
DataProcessor
的实例成员时,ProcessItem
调用是否是线程安全的?lock(obj)
方法的dataUpdater.Update
调用周围添加ProcessItem
会解决线程安全问题,前提是该方法中的其余处理不会需要是线程安全的吗?DataUpdater
(及其方法)设为静态吗?这会消除线程安全问题吗?DataUpdater
的新实例,并将其作为参数传递给ProcessItem
?修改
为Update
方法添加了代码。
答案 0 :(得分:0)
当使用数据库更新程序作为DataProcessor的实例成员时,ProcessItem调用是否是线程安全的?
不能肯定地说,因为您没有准确地告诉我们Update()
做了什么,也没有IDatabaseConnection
的方法可以安全地同时调用多个线程,但它很可能不是线程安全的。
如果没有,在ProcessItem方法中对dataUpdater.Update的调用周围添加锁(obj)是否解决了线程安全问题,前提是该方法中的其余处理不需要是线程安全的?
可能会使其线程安全,但是您需要确保IDatabaseConnection
是线程安全的,或者在循环运行时,在此循环之外的程序中的其他任何位置不同时使用。
应该将类DataUpdater(及其方法)设为静态吗?这会消除线程安全问题吗?
不,制作一些静态的东西并不能神奇地使某些东西变得安全,而是方法中的代码使其安全,如果它是静态的或没有效果。
是否应该在循环中创建一个新的DataUpdater实例并将其作为参数传递给ProcessItem?
是和否,Parallel.ForEach
has overloads具有在每个线程的开头和结尾发生的特殊委托,它包含线程本地对象,并且该对象在每个调用的线程内重用。你应该在那里创建新的更新程序。
在下面的示例中,我做了一些小的假设,someConnection
不是线程安全的,但提供了.Clone()
方法,该方法创建了一个可以与旧连接同时使用的新连接。此外,我假设.Close()
上有一个DataUpdater
方法,可以调用该方法来关闭传入的连接。
public void Process()
{
Parallel.ForEach(itemCollection,
//This is called at the creation of a thread, may be called on many threads at once.
() =>
{
lock(someConnection)
{
var newConnection = someConnection.Clone();
return new DataUpdater(newConnection)
}
};
(item, loopState, dataUpdater) => //dataUpdater is the thread local copy
{
ProcessItem(item, dataUpdater);
// (commented as not relevant to question)
// if (mustStop())
// {
// loopState.Break();
// }
//This will get passed in to the next item or the cleanup method.
return dataUpdater;
}
//This is called at the end of a thread after it has finished processing items.
, (dataUpdater) => dataUpdater.Close()
);
}
private void ProcessItem(Item item, DataUpdater dataUpdater)
{
// some processing...
dataUpdater.Update(item);
}
附录A:
ForEach在内部进行的简化示例。
public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<TSource> source,
Func<TLocal> localInit,
Func<TSource, ParallelLoopState, TLocal, TLocal> body,
Action<TLocal> localFinally)
{
ParallelLoopState[] loopStates;
IEnumerable<TSource>[] jobBatches;
// (SNIP)
Task.Run(() => WorkerThread(jobBatchs[0], localInit, body, localFinally, loopStates[0]))
Task.Run(() => WorkerThread(jobBatchs[1], localInit, body, localFinally, loopStates[1]))
// (SNIP)
}
private static void WorkerThread<TSource, TLocal>(IEnumerable<TSource> jobBatch,
Func<TLocal> localInit,
Func<TSource, ParallelLoopState, TLocal, TLocal> body,
Action<TLocal> localFinally,
ParallelLoopState loopState)
{
TLocal localData = localInit();
foreach(TSource data in jobBatch)
{
if(loopState.ShouldExitCurrentIteration)
break;
localData = body(data, loopState, localData);
}
localFinally(localData);
}