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

C# 为什么Windows.System.Threading.ThreadPoolTimer.Cancel()不起作用,c#,windows-store-apps,C#,Windows Store Apps,更新:这在Windows 10中正常工作 下面是一个简单的例子: void testcase() { if (myTimer != null) myTimer.Cancel(); myTimer = ThreadPoolTimer.CreateTimer( t => myMethod(), TimeSpan.FromMilliseconds(4000)

更新:这在Windows 10中正常工作

下面是一个简单的例子:

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

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

    void myMethod()
    {
         myTimer = null;
         //some work
    }
它应该做的是确保myMethod的调用频率不超过4s中的一次,并且如果已经有一个新的testcase调用,则不应该调用myMethod。类似于桌面上的.net定时器的东西是可能的。然而,对testcase的新调用并不会阻止以前计划的myMethods运行。我有一个简单的解决方法,向myMethod添加integer callid参数并跟踪它。但上述方法应该有效,但事实并非如此


我做错什么了吗?有没有人对如何做到这一点有更好的想法?

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


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

您正在寻找的是所谓的去Bouncing

实现此功能的一个简单方法是使用System.Threading.Timer,它有一个方便的更改用于重置它

如果您想将其抽象到自己的计时器类中,它将类似于:

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();
    }
}
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();
    }
}
然后,您的测试用例将变成:

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();
}
[更新]

从您的评论来看,似乎您希望在每次重置时更改回调方法,并且只调用最后一个方法。如果是这种情况,您可以将代码更改为:

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();
    }
}
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();
    }
}
调用重置方法时,可以使用lambda捕获各种方法调用,而不仅仅是操作方法:

正如第二个计时器代码段的注释中所述,该代码不是线程安全的,因为当在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();
    }
}

为了回答我自己可能出现的问题,Cancel似乎只用于取消周期计时器,以避免进一步重复。我不能说文档确实如此,但它似乎是这样工作的。所以,若计时器不是像本例中那个样周期性的,那个么取消就并没有效果


更新:这在Windows 10中正常工作。

你究竟如何区分两者的区别?您可以取消计时器,但随后立即创建另一个也调用该方法的计时器。将timer变量设置为null只会阻止您取消计时器。好吧,我知道其中的区别,因为有些东西没有正常工作,然后我在方法中添加了integer callerid参数,该参数在每个新计时器之前增加,然后我可以看到是哪个计时器引发了该方法。当然,我可以这样解决这个问题,但这不是一个问题。问题是我说的对吗,这应该是可行的,但事实并非如此?还有,它到底是如何与Winforms定时器一起工作的您的代码应该可以工作您如何调用testcase函数?你没有在代码的任何地方使用myTimer吗?嗨,佩德罗,谢谢你,我也认为它应该工作,这就是为什么我在这里问。myTimer不在其他地方使用。我在应用程序的不同部分使用了这种模式,但有一种情况是,当ObservableCollection发生更改时,它会被调用。感谢您是第一个真正尝试思考这个问题的人。然而,这不是答案。问题是myMethod根本不应该执行,或者它的执行应该推迟到某个时间点,所以不管它的代码是什么,如果我每4秒钟调用一次testcase,它都不应该启动。每次调用testcase都会在4s后启动。不应该是这样的。TimperPool.CreateTimer似乎不存在?Web搜索不提供某些结果,Visual Studio无法解析此类,因此至少对于Windows应用商店应用程序,它不存在。这里是指向ThreadpoolTimer的链接。CreateTimer:嗨,伊万:让我确保我了解了问题所在。所以,您不希望myMethod每4秒就被调用一次。相反,您需要一种方法来保证无论谁调用您的方法,它的执行频率都不会超过每4秒一次,并且如果有人在方法执行时调用了您的方法,则该调用将被忽略,不会在4秒后排队执行。是吗?基本上是对的。但是,如果myMethod正在执行,则不应忽略调用,如果4s中没有人调用testcase,则应执行调用。好的,这可能被标记为应答,我忽略了Windows应用商店应用程序中存在System.Threading.Timer,我已经
我之前已经看过了,所以这些信息就足够了。但是,如果您有任何想法的话,了解ThreadPoolTimer为什么不工作会很有用?我玩了一点-实际上我以前使用过System.Timers.Timer。由于我有不同的myMethods,当我需要传递多个参数时,这个例子似乎不是最好的解决方案,而如果我没有错的话,状态参数是可能的,它将需要声明自定义类,而且所有这些都太麻烦了。。。我想没有真正好的解决方案,但这是最好的主意,如果在一天内什么都没有发生,它将被标记为答案……您能更好地描述您的用例吗?如何决定最终调用哪个回调方法?您是否有多个具有不同回调的调用者,并且您只想调用触发重置的最后一个调用者?使用带有动作签名的匿名回调方法可以提供任意复杂的回调代码并捕获任何需要的参数,只是我不完全理解您打算如何使用它。对于这样的模式,只有一种参数组合是可能的,如myMethodint par1、int par2、,但是在应用程序的不同部分可能会有不同的模式,但它们会从不同的测试用例或示例中的不同类调用。如果是这样,那就是一个bug。文档和提供的示例代码表明Cancel将阻止处理程序运行。考虑调用第三参数的CureTimeTebug,终止处理程序,看看它是否对您有帮助。当然不是这样。定期和一次性使用ThreadPoolTimer都支持取消。如果不再需要任何类型的计时器,则可以且应该通过“取消”取消它们。这里唯一的例外是已经调用了其回调的一次性计时器,在这种情况下,调用Cancel将不起任何作用。