运行后台工作,但确保在任何给定时间只能运行一次

时间:2017-08-24 15:54:31

标签: c# task

我想开始一些背景工作,我不需要保留任何参考。开始忘记。但是,我想确保此后台工作仅在给定的任何时间运行一次。

假设只从一个线程调用StartBackgroundWork,你能改进这个解决方案吗?你觉得这有什么问题吗?

private bool _busy;

private void StartBackgroundWork()
{
    if(_busy)
    {
        return;
    }
    _busy = true;
    Task.Factory.StartNew(() => {
        try
        {
            // Do work
        }
        finally
        {
            _busy = false;
        }
    }, TaskCreationOptions.LongRunning);
}

我应该避免在这种情况下使用Task吗?

2 个答案:

答案 0 :(得分:1)

这种方法可能会出现一个问题,即如果StartBackgroundWork调用在后台任务刚刚清理时非常接近,那么:

  

_busy = false; -happens-before - if(_busy)

可能无法保留。所以你可能会错过一些触发器。

只要StartBackgroundWork ... 只从一个线程调用 ...你就不会有重叠执行,但是当这个条件被违反时,安全就会发生。请注意,此代码可能会在将来维护,有人可能会在不考虑(或理解)线程影响的情况下调用该方法。这种类型的保证对于Web应用程序来说尤其困难。

@GhostTW提议的一个解决方案是在_busy下的lock上执行操作。锁定确实会对性能产生影响,但维护起来很干净。

实现相同目标的智能方法是将标志声明为volatile。这是超级快,但它实现的方式是,嗯......扭曲,轻微的误用会造成更多的伤害而不是好处。如果性能不是微秒级关键,那么建议使用锁定。

理想情况下,私有方法本身不需要是线程安全的,但是您的业务规则会说明并行允许的内容以及不允许的内容。这使您的方法线程感知。让它成为线程安全的是您的选择。根据业务规则的难易程度,您可能无法做出选择。要问的问题:(1)如果我们错过触发器怎么办?病人死了吗? (2)如果任务最终并行运行两次怎么办?病人死了吗? (3)如果有人查看我的代码怎么办?那会质疑我的咨询费吗? (最后一个是个笑话)

答案 1 :(得分:1)

你需要锁定以确保没有竞争条件发生。

private bool _busy;
private object locker = new object();

private void StartBackgroundWork()
{
    lock (this.locker)
    {
        if (_busy)
        {
            return;
        }
        _busy = true;
    }

    Task.Factory.StartNew(() =>
    {
        try
        {
            // Do work
        }
        finally
        {

            lock (this.locker)
                _busy = false;
        }
    }, TaskCreationOptions.LongRunning);
}