我想开始一些背景工作,我不需要保留任何参考。开始忘记。但是,我想确保此后台工作仅在给定的任何时间运行一次。
假设只从一个线程调用StartBackgroundWork
,你能改进这个解决方案吗?你觉得这有什么问题吗?
private bool _busy;
private void StartBackgroundWork()
{
if(_busy)
{
return;
}
_busy = true;
Task.Factory.StartNew(() => {
try
{
// Do work
}
finally
{
_busy = false;
}
}, TaskCreationOptions.LongRunning);
}
我应该避免在这种情况下使用Task
吗?
答案 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);
}