2

我有一个类需要定期对其私有状态(ConcurrentDictionary)进行一些处理。我正在通过Task.Factory.StartNew(doStuff). 因为这个任务是修改集合,所以我想确保 doStuff 只在条件为真时执行一次。它所做的工作可以安全地忽略竞争条件,但我不希望冗余任务占用资源并尽可能挤出同步。现在我通过双重检查锁定来控制它,它工作正常,但是在方法的末尾添加非常冗长的代码:

public void method()
{
    ...
    // do the method related stuff. Upon executing this method, the condition for
    // executing the task could become true, so it is appropriate to try to start it,
    // even though it is not directly related to the semantics of this method

    ...

    // now see if the task should be run
    if (clock.Now > timestamp.Plus(interval) && periodicTask == null)
    {
        lock (lockObject)
        {
            if (periodicTask == null) 
                periodicTask = Task.Factory.StartNew(this.DoStuff);
        }
    }
}
private void DoStuff()
{      
    doActualProcessing();

    //signal that task is complete
    timestamp = clock.Now;
    periodicTask = null;
}

这种设置可以防止 DoStuff 被额外调用,但它看起来太冗长而且有点脏。理想情况下,我想封装这个单调用逻辑,这样我就可以做这样的事情:

public void method()
{

    // do the method related stuff.
    ...

    // now see if the task should be run
    startTaskExactlyOnce(DoStuff);
}

我看到的唯一选择可能是Lazy<T>用来完成此任务,但在这里似乎不合适,因为:1.我没有初始化对象,我正在运行任务 2.任务将定期运行,a每个周期单次 3. 我只需要启动任务,所以没有翻译用于Lazy<t>.Value导致执行

4

3 回答 3

2

通过Lazy<T>Interlocked.

Lazy<Task> periodicTaskLazy;

void RunDoStuffWithoutConcurrency()
{
    Lazy<Task> periodicTaskLazyLocal = 
         new Lazy<Task>(() => Task.Factory.StartNew(this.DoStuff));
    Interlocked.CompareExchange<Lazy<Task>>(
        ref periodicTaskLazy,
        periodicTaskLazyLocal,
        null);
    Task actualTask = periodicTaskLazy.Value;
}

void DoStuff()
{
    //....
    Interlocked.Exchange(ref periodicTaskLazy,null);
}

虽然我怀疑TPL Dataflow可能是这种任务仲裁的更好选择。

于 2013-07-29T19:23:33.967 回答
2

就我个人而言,我会在整个双重检查锁定方面投入精力。我认为它不会为您支付的风险溢价购买任何实质性的东西。话虽如此,您的具体实现可能会正常工作。但是,这大多是偶然的。继续阅读。

首先,写入periodicTaskinDoStuff将立即发布,因为 1)x86 硬件上的写入已经具有 volatile 语义,并且 2)在特殊同步点为您生成隐式内存屏障,即Task在这种情况下 a 的结尾,所以即使在较弱的内存模型环境,事情的写端可能没问题。

其次,读取periodicTaskin可能会method绕过任何优化,因为(假设它像clock.NowDateTime.Now

请注意我是如何一直使用“可能”这个词的?在得出上述结论时,我做了几个假设。可能是我大错特错了。不过,我可以肯定地说,我的假设很可能是正确的。关键是为什么要冒险。即使我是对的,当下一个程序员进入那里时,这段代码也很容易中断,因为他不明白你的意图是什么。

让我们回到开始的双重检查锁定模式的原因。正如我所说,这并不值得。当不是时,您希望多久method运行一次?在您获得任何有意义的收益之前,这种情况必须大大超过所有其他情况(通过......我不知道......也许是 100,000 比 1 或其他东西)。a 的成本非常小,因此无论您玩什么花样,都必须物超所值,才能使付出的努力变得物有所值。我只是没有看到在您的场景中发生这种情况。periodicTasknulllock

于 2013-07-30T00:25:38.590 回答
0

如果只希望一个线程在修改字典,为什么要使用并发实现?对我来说,您似乎只需要计时器。或者您可以在处理方法结束时安排下一次运行。

于 2013-07-29T19:04:36.047 回答