我有一个填充共享集合的方法,我在lock
和Task
中调用它,如下所示:
void PopulateCollection()
{
Task.Factory.StartNew(() =>
{
lock (_padlock)
{
_sharedDictionary = GetSharedDictionary();
}
});
}
无论何时阅读,我都会在该集合周围使用相同的lock(_padlock)
。
我遇到的问题是Task.Factory
可能没有启动它的任务(因此可能没有获得锁定)。这导致了竞争条件。
这样的读者方法存在:
void ReadCollection()
{
lock(_padlock)
{
DoSomethingWithCollection(_sharedDictionary);
}
}
所以我的问题是我有这样的代码:
...
PopulateCollection();
... // some things happen
ReadCollection();
...
我无法保证在读取之前填充集合,因为我无法保证在读取集合之前已启动任务(因此已获得锁定)。
在获得锁定之前,我不想继续。
答案 0 :(得分:4)
您没有正确使用TPL,因此您的问题。在使用TPL时,您通常不应该非常使用lock
。
不是在任务中改变共享变量来设置操作的结果,而是让操作的结果设置任务的结果。然后,您可以保留对该任务的引用,并在计算它时使用其值。
Task<Dictionary<TKey,TValue>> PopulateCollection()
{
return Task.Factory.StartNew(() => GetSharedDictionary());
}
然后,您可以为该任务附加一个延续,以便在计算结果后对结果执行某些操作:
PopulateCollection()
.ContinueWith(task => DoSomethingWithCollection(t.Result));
答案 1 :(得分:2)
我要做的是返回Task
,以便您可以等待操作完成,而不是按照您的建议启动它。类似的东西:
Task PopulateCollection()
{
return Task.Factory.StartNew(() =>
{
lock (_padlock)
{
_sharedDictionary = GetSharedDictionary();
}
});
}
var populateTask = PopulateCollection();
... // some things happen
populateTask.Wait();
ReadCollection();
如果您只对群集进行一次填充然后重复使用,则另一种选择是将_sharedDictionary
更改为Task<YourDictionaryType>
:
void PopulateCollection()
{
_sharedDictionaryTask = Task.Factory.StartNew(() => GetSharedDictionary());
}
void ReadCollection()
{
DoSomethingWithCollection(_sharedDictionaryTask.Result);
}
Task
将在此处处理必要的同步。
答案 2 :(得分:1)
如果您希望确保在任务之外的流程继续之前获得任务内的锁定,则可以使用AutoResetEvent:
var waitHandle = new AutoResetEvent(false);
Task.Factory.StartNew(() =>
{
lock (_padlock)
{
waitHandle.Set();
_sharedDictionary = GetSharedDictionary();
}
});
waitHandle.WaitOne();