我正在研究下面的代码,并试图尽可能快地制作它。
每次在系统中触发事件时,都会调用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);
}
}
}
}
答案 0 :(得分:1)
哦,我不会那样做!将'if(currentTime'和'shouldRun'的东西放回锁内。
不要在锁外更改/检查状态 - 它肯定会搞砸。
在这种情况下,刚刚将'shouldRun'设置为true的线程可能会被另一个进入的线程反转,并在锁定之前再次将'shouldRun'设置为false。然后第一个线程没有进入'StartNew',后面的线程也不会,因为第一个线程将_lastTriggered设置为当前时间。
OTOH :)因为'shouldRun'是一个自动变量而不是一个字段,它不是状态。只有一个线程可以进入锁内,仔细检查间隔并更新_lastTriggered时间。
我不喜欢这种复核,但目前看不出为什么它不起作用。
答案 1 :(得分:0)
使用Monitor.TryEnter。
if (Monitor.TryEnter(_lock))
{
try
{
if (currentTime.Subtract(_lastTriggered).Duration().TotalMinutes >
_autoReduceInterval)
{
_lastTriggered = currentTime;
shouldRun = true;
}
}
finally
{
Monitor.Exit(_lock);
}
}
答案 2 :(得分:0)
避免锁定并使用Interlocked.Exchange会不会有帮助?
E.g。
private long _lastTriggeredTicks;
private DateTime lastTriggered
{
get
{
var l = Interlocked.Read( ref _lastTriggeredTicks );
return new DateTime( l );
}
set
{
Interlocked.Exchange( ref _lastTriggeredTicks, value );
}
}
根据我的理解Interlocked
is faster than a lock
陈述。
答案 3 :(得分: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)
}
}
}
答案 4 :(得分: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);
}
}