使用System.Threading.Timer和Monitor进行线程安全执行

Gra*_*mas 5 .net c# thread-safety

使用System.Threading.Timer从a中旋转的线程的结果ThreadPool,这意味着如果计时器的执行间隔在线程仍在按先前请求的顺序处理时到期,则将委托相同的回调在另一个线程上执行.在大多数情况下,这显然会导致问题,除非回调是可重入的,但我想知道如何以最好的(意味着安全的)方式去做.

假设我们有以下内容:

ReaderWriterLockSlim OneAtATimeLocker = new ReaderWriterLockSlim();

OneAtATimeCallback = new TimerCallback(OnOneAtATimeTimerElapsed);
OneAtATimeTimer = new Timer(OneAtATimeCallback , null, 0, 1000);
Run Code Online (Sandbox Code Playgroud)

整个shebang应该被锁定,如下:

private void OnOneAtATimeTimerElapsed(object state)
{
    if (OneAtATimeLocker.TryEnterWriteLock(0))
    {
        //get real busy for two seconds or more

        OneAtATimeLocker.ExitWriteLock();
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,应该只管理输入,并踢出'侵入者',如下:

private void OnOneAtATimeTimerElapsed(object state)
{
    if (!RestrictOneAtATime())
    {
        return;
    }

    //get real busy for two seconds or more

    if(!ReleaseOneAtATime())
    {
        //Well, Hell's bells and buckets of blood!
    }       
}

bool OneAtATimeInProgress = false;

private bool RestrictToOneAtATime()
{
    bool result = false;
    if (OneAtATimeLocker.TryEnterWriteLock(0))
    {
        if(!OneAtATimeInProgress)
        {
            OneAtATimeInProgress = true;
            result = true;
        }
        OneAtATimeLocker.ExitWriteLock();
    }
    return result;
}

private bool ReleaseOneAtATime()
{
    bool result = false;
    //there shouldn't be any 'trying' about it...
    if (OneAtATimeLocker.TryEnterWriteLock(0))
    {            
        if(OneAtATimeInProgress)
        {
            OneAtATimeInProgress = false;
            result = true;
        }
        OneAtATimeLocker.ExitWriteLock();
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

第一个是否有任何性能影响因为它锁定了方法的范围?

第二个甚至提供了人们可能认为的安全性 - 这是逃避我的吗?

有没有其他方法可靠地,最好?

Han*_*ant 10

有很多方法可以解决这个问题.一种简单的方法是不要使定时器定期,只需设置dueTime参数即可使其成为一次定时器.然后在finally块中的回调中重新启用计时器.这可以保证回调不能同时运行.

这当然使得区间可以通过回调的执行时间来变量.如果这是不可取的并且回调仅偶尔花费比计时器周期更长的时间,那么简单的锁定将完成工作.另一个策略是Monitor.TryEnter,如果它返回false,就放弃回调.这些都不是特别优越,选择你最喜欢的.