【问题标题】:Dispose Timer inside of anonymous method在匿名方法中配置 Timer
【发布时间】:2012-10-17 01:55:32
【问题描述】:

我有一段代码会被相对频繁地调用。在它被调用之前,我需要延迟 2000 毫秒。

首先想到的是每次调用该方法时创建/处理一个计时器。

为此,我使用了一个计时器(参见代码)。我的问题是......在下面的匿名方法中调用 Dispose 有任何危险/问题吗?有更好方法的建议?

执行以下操作有什么缺点吗?坏主意?

delayTimer = new Timer() { Interval = 2000 };
{
    delayTimer.Tick += (sender2, e2) => 
    { 
        ((Timer)sender2).Stop();     
        MessageBox.Show("Do something after 2000ms"); 
        delayTimer.Dispose(); 
    };
}

【问题讨论】:

  • 如果在Dispose调用之前出现异常会怎样?
  • 你可以同步使用 System.Threading.Thread.Sleep...
  • 好点...将它包装在 Try finally 中应该涵盖了...我可以在那里处理。正在编辑... 编辑:实际上将它包装在 try 中是行不通的,因为它只会立即处理 Timer。
  • Joe 我担心调用 Thread.Sleep,因为这个应用程序中已经运行了许多线程并且它是 GUI 密集型的。我担心锁定 UI 线程。这对我来说是一个无效的担忧吗?
  • 为什么是 2 秒?在执行“do_something_function()”之前是否需要执行一个过程?

标签: c# .net timer dispose anonymous


【解决方案1】:

您可以做不同的事情是将计时器的 Dispose 移到匿名方法的前面。这样,即使您稍后在方法中抛出异常,您仍然已 Disposed 计时器。我以前使用过这种模式,这是一种相当干净的方式来获得延迟回调。

如果您使用的是 C#5,有一个非常好的 Task.Delay 方法,您可以 await 在异步方法中获取计时器回调。这通常用于结合调用 WaitAny 来实现超时,如下所示:

    public static async Task WithTimeout(this Task task, int timeout, string timeoutMessage = null, params object[] args)
    {
        var timeoutTask = Task.Delay(timeout);
        if (await Task.WhenAny(task, timeoutTask) == timeoutTask)
            throw new TimeoutException(timeoutMessage == null ? "Operation timed out" : string.Format(timeoutMessage, args));

        await task;
    }

【讨论】:

  • +1 用于处理建议和 C#5。我希望我可以完成任务……这会让生活变得更轻松。不幸的是,我现在只能保证 .Net 4.0。
【解决方案2】:

非常奇怪的任务,但没关系。

如果你真的需要这样做,你真的想使用一个计时器,并且该代码块确实会被非常频繁地调用,而不是你应该考虑使用计时器缓存。

每次调用代码块时,都应该检查缓存是否包含空闲计时器。如果是,则仅使用第一个可用的,否则,创建一个新的并将其放入缓存中。此外,您将需要另一个计时器,该计时器将一直工作,并会定期检查缓存中未使用的计时器。如果某些计时器或计时器在 10 秒内未使用,则将其丢弃并从缓存中删除。这种方法将显着减少创建的计时器实例的数量。

【讨论】:

  • 感谢您的回复。好奇为什么你觉得这是一个奇怪的任务。如果奇怪,除了使用计时器之外,您还有其他建议吗?我需要这样做的原因是因为我有一个工作线程处理任务。当该线程开始执行时,我需要通过另一个线程向用户直观地显示某些内容。这类似于正在显示的“处理”动画。但是,我不想立即显示它。我想要在显示之前有 2000 毫秒的延迟。这有与用户体验相关的正当理由。谢谢
  • 还有为什么需要缓存定时器?这比动态创建/处理更干净吗? (不是批判性陈述......只是一个问题):)
  • Sergey 的想法就像一个计时器“池”。它就像一个连接池,但只有定时器。这不是一个坏主意,因为它促进了重用。但是,我不知道 Timer 是否真的是资源密集型的。
  • @user1631520 - 为什么不在显示线程的开头调用 Thread.Sleep(2000) 呢?这不会启动另一个线程,它只会将当前线程延迟 2 秒。
【解决方案3】:

仅当您非常频繁地创建计时器(每秒数百次)时才需要缓存,因为多次创建和处置像 Timer 这样的重对象(它使用一些系统资源)可能会导致性能问题。但是从您的详细描述来看,很明显您不会经常创建计时器,因此您可以保留您的解决方案。但是不使用定时器,你可以使用RX'sInterval observable 集合,你所有的代码都会被缩短为1个字符串:

Observable.Interval(TimeSpan.FromSeconds(2)).Take(1).Subscribe(_ => MessageBox.Show("Do something after 2000ms"));

【讨论】:

  • 最好使用Timer 而不是Interval/.Take(1) 对。您的代码如下所示:Observable.Timer(TimeSpan.FromSeconds(2)).Subscribe(/* */);
【解决方案4】:

使用您的代码创建一千个计时器时,我没有遇到任何严重的性能问题。

在匿名方法中处理Timer(或任何IDisposable)完全没有问题。

但是,您的代码并不是以最好的方式编写的。试试这个实现:

    var delayTimer = new Timer()
    {
        Interval = 2000,
        Enabled = true,
    };
    EventHandler tick = null;
    tick = (_s, _e) => 
    { 
        delayTimer.Tick -= tick;
        delayTimer.Stop();     
        delayTimer.Dispose(); 
        MessageBox.Show("Do something after 2000ms");
    };
    delayTimer.Tick += tick;

在尝试处置计时器之前分离事件总是一个好主意。事实上,可能有很多次无法分离导致 GC 无法正确清理,并且可能会出现内存泄漏。

尽管如此,我确实喜欢 Rx 对这个问题的回答是最干净的方法。尽管除非你这样做,否则使用 Rx 不会在 UI 线程上编组回调:

Observable
    .Timer(TimeSpan.FromSeconds(2.0))
    .ObserveOn(this) // assuming `this` is your form
    .Subscribe(_ => MessageBox.Show("Do something after 2000ms"));

简单得多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多