为什么Windows.System.Threading.ThreadPoolTimer.Cancel()不起作用
本文关键字:不起作用 Cancel ThreadPoolTimer Windows System Threading 为什么 | 更新日期: 2023-09-27 18:11:26
更新:此操作在Windows 10中正常工作
下面是一个简单的例子:
void testcase()
{
if (myTimer != null)
myTimer.Cancel();
myTimer = ThreadPoolTimer.CreateTimer(
t => myMethod(),
TimeSpan.FromMilliseconds(4000)
);
}
void myMethod()
{
myTimer = null;
//some work
}
它应该做的是确保myMethod不能被更频繁地调用,而且如果已经有对testcase的新调用,则不应该调用myMethod。类似于桌面的。net计时器是可能的。但是,对testcase的新调用不会阻止先前计划的myMethods运行。我有一个简单的解决方法,将整数callid参数添加到myMethod并跟踪它。但是上面这个应该可以工作,但它没有。
我做错了什么吗?还有人有更好的主意吗?
你正在寻找的东西被称为debouting,至少在javascript中是这样。
实现它的一个简单方法是使用System.Threading.Timer
代替,它有一个方便的Change
用来重置它。
如果你想把它抽象到你自己的定时器类中,它看起来像这样:
public class DebounceTimer : IDisposable
{
private readonly System.Threading.Timer _timer;
private readonly int _delayInMs;
public DebounceTimer(Action callback, int delayInMs)
{
_delayInMs = delayInMs;
// the timer is initially stopped
_timer = new System.Threading.Timer(
callback: _ => callback(),
state: null,
dueTime: System.Threading.Timeout.Infinite,
period: System.Threading.Timeout.Infinite);
}
public void Reset()
{
// each call to Reset() resets the timer
_timer.Change(
dueTime: _delayInMs,
period: System.Threading.Timeout.Infinite);
}
public void Dispose()
{
// timers should be disposed when you're done using them
_timer.Dispose();
}
}
您的测试用例将变成:
private DebounceTimer _timer;
void Init()
{
// myMethod will be called 4000ms after the
// last call to _timer.Reset()
_timer = new DebounceTimer(myMethod, 4000);
}
void testcase()
{
_timer.Reset();
}
void myMethod()
{
//some work
}
public void Dispose()
{
// don't forget to cleanup when you're finished testing
_timer.Dispose();
}
(更新)从你的评论来看,似乎你想在每次重置时更改回调方法,并且只调用最后一个。如果是这种情况,您可以将代码更改为:
class DebounceTimer : IDisposable
{
private readonly System.Threading.Timer _timer;
private readonly int _delayInMs;
private Action _lastCallback = () => { };
public DebounceTimer(int delayInMs)
{
_delayInMs = delayInMs;
// the timer is initially stopped
_timer = new System.Threading.Timer(
callback: _ => _lastCallback(),
state: null,
dueTime: System.Threading.Timeout.Infinite,
period: System.Threading.Timeout.Infinite);
}
public void Reset(Action callback)
{
_timer.Change(dueTime: _delayInMs, period: System.Threading.Timeout.Infinite);
// note: no thread synchronization is taken into account here,
// a race condition might occur where the same callback would
// be executed twice
_lastCallback = callback;
}
public void Dispose()
{
_timer.Dispose();
}
}
当调用Reset
方法时,您可以使用lambda来捕获各种方法调用(不仅仅是Action
方法):
void testcase()
{
_timer.Reset(() => myMethod());
}
void othertestcase()
{
// it's still a parameterless action, but it
// calls another method with two parameters
_timer.Reset(() => someOtherMethod(x, y));
}
正如第二个计时器片段的注释中所述,该代码不是线程安全的,因为当回调引用在Reset
方法中被更改时,计时器处理程序可能已经在一个单独的线程上运行(或即将运行),这意味着相同的回调将被执行两次。
一个稍微复杂一点的解决方案是在更改回调时进行锁定,并进行额外的检查,如果从上次调用到重置已经经过了足够的时间。最后的代码看起来像这样(可能还有其他同步的方法,但这个方法非常简单):
class DebounceTimer : IDisposable
{
private readonly System.Threading.Timer _timer;
private readonly int _delayInMs;
private readonly object _lock = new object();
private DateTime _lastResetTime = DateTime.MinValue;
private Action _lastCallback = () => { };
public DebounceTimer(int delayInMs)
{
_delayInMs = delayInMs;
// the timer is initially stopped
_timer = new System.Threading.Timer(
callback: _ => InvokeIfTimeElapsed(),
state: null,
dueTime: System.Threading.Timeout.Infinite,
period: System.Threading.Timeout.Infinite);
}
private void InvokeIfTimeElapsed()
{
Action callback;
lock (_lock)
{
// if reset just happened, skip the whole thing
if ((DateTime.UtcNow - _lastResetTime).TotalMilliseconds < _delayInMs)
return;
else
callback = _lastCallback;
}
// if we're here, we are sure we've got the right callback - invoke it.
// (even if reset happens now, we captured the previous callback
// inside the lock)
callback();
}
public void Reset(Action callback)
{
lock (_lock)
{
// reset timer
_timer.Change(
dueTime: _delayInMs,
period: System.Threading.Timeout.Infinite);
// save last reset timestamp
_lastResetTime = DateTime.UtcNow;
// set the new callback
_lastCallback = callback;
}
}
public void Dispose()
{
_timer.Dispose();
}
}
问题是你在myMethod中设置了timer = null。这保证了它将在下一次调用testCase时为空(所以它不会被取消)。
应该使用TimerPool。CreateTimer创建单实例计时器。它只会发射一次。当工作进程结束时,它应该做的最后一件事是初始化一个新的计时器。
为了回答我自己可能的问题,似乎Cancel()仅用于取消周期计时器的进一步重复。我不能说文档中是这么说的,但它似乎是这样工作的。因此,如果timer不是周期性的,则Cancel不起作用。