在继续之前如何确保任务已经开始?

时间:2014-05-21 16:52:53

标签: c# .net asynchronous locking task-parallel-library

我有一个填充共享集合的方法,我在lockTask中调用它,如下所示:

void PopulateCollection()
{
    Task.Factory.StartNew(() =>
        {
            lock (_padlock)
            {
                _sharedDictionary = GetSharedDictionary();
            }
        });
}

无论何时阅读,我都会在该集合周围使用相同的lock(_padlock)

我遇到的问题是Task.Factory可能没有启动它的任务(因此可能没有获得锁定)。这导致了竞争条件。

这样的读者方法存在:

void ReadCollection()
{
    lock(_padlock)
    {
        DoSomethingWithCollection(_sharedDictionary);
    }
}

所以我的问题是我有这样的代码:

...
PopulateCollection();
... // some things happen
ReadCollection();
...

我无法保证在读取之前填充集合,因为我无法保证在读取集合之前已启动任务(因此已获得锁定)。

在获得锁定之前,我不想继续。

3 个答案:

答案 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();