为什么Windows.System.Threading.ThreadPoolTimer.Cancel()不起作用

时间:2014-07-03 07:30:54

标签: c# windows-store-apps

更新:这适用于Windows 10。

这是一个简单的例子:

    void testcase()
    {
         if (myTimer != null)
             myTimer.Cancel();

         myTimer = ThreadPoolTimer.CreateTimer(
             t => myMethod(),
             TimeSpan.FromMilliseconds(4000)
         );
    }

    void myMethod()
    {
         myTimer = null;
         //some work
    }

它应该做的是确保mysethod不能在4s中比一次更频繁地调用,并且如果已经有更新的testcase调用,则不应该调用myMethod。与桌面上的.net计时器类似的东西是可能的。但是,对testcase的新调用并不会阻止以前安排的myMethods运行。我有一个简单的解决方法,通过向myMethod添加整数callid参数并跟踪它。但是上面的内容应该有效,而且没有。

我做错了吗?有没有人对如何做到更好?

3 个答案:

答案 0 :(得分:5)

您正在寻找的是debouncing,at least in 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));
}

如第二个计时器片段的注释中所述,代码不是线程安全的,因为当在{内部更改回调引用时,计时器处理程序可能已在单独的线程上运行(或即将运行) {1}}方法,意味着同一个回调将被执行两次。

稍微复杂的解决方案是在更改回调时锁定,并进行额外检查,以确定自上次调用重置后是否已经过了足够的时间。最终的代码看起来像这样(可能还有其他的同步方式,但这个方法非常简单):

Reset

答案 1 :(得分:0)

问题是你在myMethod中设置了timer = null。这保证了在下次调用testCase时它将为null(因此它不会被取消)。

而是使用TimerPool.CreateTimer创建单实例计时器。它只会发射一次。当您的工作进程完成时,它应该做的最后一件事是初始化一个新的计时器。

答案 2 :(得分:0)

为了回答我自己可能出现的问题,似乎Cancel()仅用于取消定期计时器以进一步重复。我不能说文档准确地说明了这一点,但它似乎就是这样的。因此,如果计时器不像这种情况那样是周期性的,则取消无效。

更新:这在Windows 10中可以正常使用。