1

我正在处理下面的代码,并试图让它尽可能快。

基本上,每次在系统中触发事件时都会调用 execute 方法。我正在测试的是查看自上次执行减少以来是否已经过去了 x 分钟。如果 x 分钟过去了,那么我们应该执行任务。

由于事件可以从任何线程触发并且发生得非常快,所以我认为在锁之外触发任务(即使它是一个任务)会比在锁中更好。

有人对如何改进有任何反馈吗?

public class TriggerReduce
{ 
    private readonly object _lock = new object();
    private readonly int _autoReduceInterval = 5;
    private DateTime _lastTriggered;

    public void Execute(object sender, EventArgs e)
    {
        var currentTime = DateTime.Now;
        if (currentTime.Subtract(_lastTriggered).Duration().TotalMinutes > _autoReduceInterval)
        {
            var shouldRun = false;
            lock (_lock)
            {
                if (currentTime.Subtract(_lastTriggered).Duration().TotalMinutes > _autoReduceInterval)
                {
                    _lastTriggered = currentTime;
                    shouldRun = true;
                }
            }

            if (shouldRun)
            {
                Task.Factory.StartNew(() =>
                {
                    //Trigger reduce which is a long running task
                }, TaskCreationOptions.LongRunning);
            }
        }
    }
}
4

5 回答 5

1

哦,我不会那样做的!将“if (currentTime”和“shouldRun”内容放回锁内。

不要在锁外更改/检查状态 - 它肯定会搞砸。

在这种情况下,刚刚将 'shouldRun' 设置为 true 的线程可能会被另一个进入并再次将 'shouldRun' 设置为 false 的线程逆转其决定,然后才会卡在锁上。然后第一个线程不会到达“StartNew”,后面的线程也不会,因为第一个线程将 _lastTriggered 设置为当前时间。

OTOH :) 因为“shouldRun”是一个自动变量而不是一个字段,所以它不是状态。只有一个线程可以进入锁,仔细检查间隔并更新_lastTriggered时间。

我不喜欢这种双重检查,但目前不明白为什么它不起作用。

于 2012-04-09T10:22:58.580 回答
0

避免锁定并改用Interlocked.Exchange会有所帮助吗?

例如

private long _lastTriggeredTicks;

private DateTime lastTriggered
{
    get 
    { 
        var l = Interlocked.Read( ref _lastTriggeredTicks );
        return new DateTime( l );
    }
    set
    {
        Interlocked.Exchange( ref _lastTriggeredTicks, value );
    }
}

据我了解Interlocked,这比lock陈述要快。

于 2012-04-09T10:26:39.213 回答
0
public class TriggerReduce //StartNew is fast and returns fast
{

    private readonly object _lock = new object();
    private readonly int _triggerIntervalMins = 5;

    private DateTime _nextTriggerAt = DateTime.MinValue;
    private bool inTrigger = false;

    public void Execute(object sender, EventArgs e)
    {
        lock (_lock)
        {
            var currentTime = DateTime.Now;
            if (_nextTriggerAt > currentTime)
                return;

            _nextTriggerAt = currentTime.AddMinutes(_triggerIntervalMins);//runs X mins after last task started running (or longer if task took longer than X mins)
        }

        Task.Factory.StartNew(() =>
        {
            //Trigger reduce which is a long running task
        }, TaskCreationOptions.LongRunning);
    }
}


public class TriggerReduce//startNew is a long running function that you want to wait before you recalculate next execution time
{

    private readonly object _lock = new object();
    private readonly int _triggerIntervalMins = 5;

    private DateTime _nextTriggerAt = DateTime.MinValue;
    private bool inTrigger = false;

    public void Execute(object sender, EventArgs e)
    {
        var currentTime;
        lock (_lock)
        {
            currentTime = DateTime.Now;
            if (inTrigger || (_nextTriggerAt > currentTime))
                return;

            inTrigger = true;
        }
        Task.Factory.StartNew(() =>
        {
            //Trigger reduce which is a long running task
        }, TaskCreationOptions.LongRunning);

        lock (_lock)
        {
            inTrigger = false;
            _nextTriggerAt = DateTime.Now.AddMinutes(_triggerIntervalMins);//runs X mins after task finishes
            //_nextTriggerAt = currentTime.AddMinutes(_triggerIntervalMins);//runs X mins after last task started running (or longer if task took longer than X mins)
        }
    }
}
于 2012-04-09T10:27:45.597 回答
0

我认为您已经有了一个相当合理的方法。最大的问题是您_lastTriggered在锁之外访问。双重检查锁定习语在这里不起作用。只需您的代码,使其看起来像这样。

public void Execute(object sender, EventArgs e)
{
    var currentTime = DateTime.Now;
    var shouldRun = false;
    lock (_lock)
    {
        TimeSpan span = currentTime - _lastTriggeed;
        if (span.TotalMinutes > _autoReduceInterval)
        {
            _lastTriggered = currentTime;
            shouldRun = true;
        }
    }

    if (shouldRun)
    {
        Task.Factory.StartNew(() =>
        {
            //Trigger reduce which is a long running task
        }, TaskCreationOptions.LongRunning);
    }
}
于 2012-04-09T13:16:55.100 回答
0

使用 Monitor.TryEnter。

if (Monitor.TryEnter(_lock))
{
    try
    {
        if (currentTime.Subtract(_lastTriggered).Duration().TotalMinutes >
            _autoReduceInterval)
        {
            _lastTriggered = currentTime;
            shouldRun = true;
        } 
    }
    finally
    {
        Monitor.Exit(_lock);
    }
}
于 2012-04-09T10:21:02.077 回答