【问题标题】:Timer and IDisposable - Extra protection in Dispose?Timer 和 IDisposable - Dispose 中的额外保护?
【发布时间】:2010-11-02 21:25:03
【问题描述】:

我有一个创建和使用 System.Threading.Timer 的类,如下所示:

using System.Threading;

public class MyClass : IDisposable
{
  private List<int> ints = new List<int>();

  private Timer t;

  public MyClass()
  {
    //Create timer in disabled state

    t = new Timer(this.OnTimer, null, Timeout.Infinite, Timeout.Infinite);
  }

  private void DisableTimer()
  {
    if (t == null) return;

    t.Change(Timeout.Infinite, Timeout.Infinite);
  }

  private void EnableTimer()
  { 
    if (t == null) return;

    //Fire timer in 1 second and every second thereafter.
    EnableTimer(1000, 1000);
  }

  private void EnableTimer(long remainingTime)
  {
    if (t == null) return;

    t.Change(remainingTime, 1000);
  }

  private void OnTimer(object state)
  {
    lock (ints) //Added since original post
    {
      DisableTimer();

      DoSomethingWithTheInts();
      ints.Clear();

      //
      //Don't reenable the timer here since ints is empty.  No need for timer
      //to fire until there is at least one element in the list.
      //
    }
  }

  public void Add(int i)
  {
    lock(ints) //Added since original post
    {
      DisableTimer();

      ints.Add(i);
      if (ints.Count > 10)
      {
        DoSomethingWithTheInts();
        ints.Clear();
      }

      if (ints.Count > 0)
      {
        EnableTimer(FigureOutHowMuchTimeIsLeft());
      }
    }
  }

  bool disposed = false;

  public void Dispose()
  {
    //Should I protect myself from the timer firing here?

    Dispose(true);
  }

  protected virtual void Dispose(bool disposing)
  {
    //Should I protect myself from the timer firing here?

    if (disposed) return;
    if (t == null) return;

    t.Dispose();
    disposed = true;
  }
}

编辑 - 在我的实际代码中,我在 Add 和 OnTimer 中的 List 上都有锁。我在简化发布代码时不小心把它们漏掉了。

本质上,我想积累一些数据,分批处理。在我积累的过程中,如果我得到 10 个项目,或者距离我上次处理数据已有 1 秒,我将处理我目前拥有的内容并清除我的列表。

由于 Timer 是 Disposable,我已经在我的班级中实现了 Disposable 模式。我的问题是:我是否需要在任一 Dispose 方法中提供额外保护以防止计时器事件触发的任何副作用?

我可以轻松地在任一或两个 Dispose 方法中禁用计时器。我知道这不一定会让我 100% 安全,因为计时器可能会在调用 Dispose 和禁用计时器之间触发。暂时不考虑这个问题,是否认为最好的做法是尽我所能保护 Dispose 方法以防止计时器执行的可能性?

现在想想,我大概也应该考虑一下,如果 Dispose 中的 List 不为空该怎么办。在对象的使用模式中 should 到那时应该是空的,但我想一切皆有可能。假设调用 Dispose 时 List 中有剩余的项目,继续尝试处理它们是好还是坏?我想我可以加入这个值得信赖的旧评论:

public void Dispose()
{
  if (ints != null && ints.Count > 0)
  {
    //Should never get here.  Famous last words!
  }
}

列表中是否有任何项目是次要的。我真的只对找出处理可能启用的 Timers 和 Dispose 的最佳实践感兴趣。

如果重要的话,这段代码实际上是在 Silverlight 类库中。它根本不与 UI 交互。

编辑:

我发现了一个看起来不错的解决方案herejsw 的一个答案建议使用 Monitor.TryEnter/Monitor.Exit 保护 OnTimer 事件,从而有效地将 OnTimer 代码置于关键部分。

Michael Burr 发布了一个似乎更好的解决方案,至少在我的情况下,通过将到期时间设置为所需的时间间隔并将周期设置为 Timeout.Infinite 来使用一次性计时器。

对于我的工作,我只想在至少一项已添加到列表中时触发计时器。因此,首先,我的计时器被禁用。进入 Add 后,禁用计时器,使其在 Add 期间不会触发。添加项目时,处理列表(如有必要)。在离开添加之前,如果列表中有任何项目(即如果列表尚未处理),请启用计时器,并将到期时间设置为剩余间隔,周期设置为Timeout.Infinite .

使用一次性计时器,甚至没有必要在 OnTimer 事件中禁用计时器,因为计时器无论如何都不会再次触发。当我离开 OnTimer 时,我也不必启用计时器,因为列表中不会有任何项目。我将等到另一个项目添加到列表中,然后再次启用一次性计时器。 谢谢!

编辑 - 我认为这是最终版本。它使用一次性计时器。当列表从零个成员变为一个成员时启用计时器。如果我们在 Add 中达到计数阈值,则会处理项目并禁用计时器。对项目列表的访问由锁保护。我对一次性计时器是否比“普通”计时器更能买到我持观望态度,除了它只会在列表中有项目并且在添加第一个项目后仅 1 秒时才会触发。如果我使用普通计时器,那么可能会发生以下顺序:快速连续添加 11 个项目。添加第 11 个会导致项目被处理并从列表中删除。假设定时器还剩 0.5 秒。再添加 1 项。时间将在大约 0.5 秒内触发,并且将处理并删除一项。使用一次性计时器,当添加 1 个项目时,计时器会重新启用,并且在整整 1 秒间隔(或多或少)过去之前不会触发。有关系吗?可能不是。无论如何,这是一个我认为相当安全的版本,可以做我想做的事情。

using System.Threading;

public class MyClass : IDisposable
{
  private List<int> ints = new List<int>();

  private Timer t;

  public MyClass()
  {
    //Create timer in disabled state

    t = new Timer(this.OnTimer, null, Timeout.Infinite, Timeout.Infinite);
  }

  private void DisableTimer()
  {
    if (t == null) return;

    t.Change(Timeout.Infinite, Timeout.Infinite);
  }

  private void EnableTimer()
  { 
    if (t == null) return;

    //Fire event in 1 second but no events thereafter.
    EnableTimer(1000, Timeout.Infinite);
  }

  private void DoSomethingWithTheInts()
  {
    foreach (int i in ints)
    {
      Whatever(i);
    }
  }

  private void OnTimer(object state)
  {
    lock (ints)
    {
      if (disposed) return;
      DoSomethingWithTheInts();
      ints.Clear();
    }
  }

  public void Add(int i)
  {
    lock(ints)
    {
      if (disposed) return;

      ints.Add(i);
      if (ints.Count > 10)
      {
        DoSomethingWithTheInts();
        ints.Clear();
      }

      if (ints.Count == 0)
      {
        DisableTimer();
      }
      else
      if (ints.Count == 1)
      {
        EnableTimer();
      }
    }
  }

  bool disposed = false;

  public void Dispose()
  {
    if (disposed) return;

    Dispose(true);
  }

  protected virtual void Dispose(bool disposing)
  {
    lock(ints)
    {
      DisableTimer();
      if (disposed) return;
      if (t == null) return;

      t.Dispose();
      disposed = true;
    }
  }
}

【问题讨论】:

  • 如果你不防范已知的竞争条件,它最终回来咬你。

标签: c# .net silverlight timer idisposable


【解决方案1】:

我在这段代码中看到了一些严重的同步问题。我认为您要解决的是经典的读写器问题。使用当前的方法很可能您会遇到一些问题,例如如果有人在处理列表时尝试修改列表怎么办?

我强烈建议使用 .net 的并行扩展或(使用 .net 3.5 或更早版本时)使用诸如 ReaderWriterlock 之类的类,甚至是简单的 Lock 关键字。 还要记住 System.Threading.Timer 是一个异步调用,因此 OnTimer 是从单独的线程(来自 .net ThreadPool)调用的,所以您肯定需要一些同步(比如可能锁定集合)。

我会看看 .NET 4 中的并发集合,或者使用 .net 3.5 或更早版本中的一些同步原语。不要在 ADD 方法中禁用定时器。在 OnTimer 中编写正确的代码,例如

lock(daObject)
{
    if (list.Count > 10)
        DoSTHWithList;
}

此代码是最简单的(尽管绝对不是最佳的)应该可以工作。类似的代码也应该添加到 Add 方法中(锁定集合)。 希望它有帮助,如果不是给我发信息。 卢克

【讨论】:

  • 谢谢。当我简化我的实际发布代码时,我忽略了锁。只是好奇,为什么我不应该在 OnTimer 期间禁用计时器?间隔应该是这样,计时器在 OnTimer 期间无论如何都不会触发,但是如果间隔恰好很短并且 OnTimer 需要很长时间,OnTimer 可能会触发,然后我会在哪里?我想另一种方法是在 OnTimer 中有一个标志,可以让我短路任何可重入的 OnTimer 调用。
  • 如果第二个(和后续的)准时触发,而第一个仍在处理您应该以这样的方式编写 OnTimer,它会做正确的事情(例如什么都不做)或者也许收集另外 10 件物品并对它们做某事(比如第一个复制了 10 件物品并解锁了收藏,现在正在处理它们,如果处理一件物品需要很长时间)。请记住,计时器在单独的线程上,所以它会发出脉冲,检查类的状态,做一些事情然后死掉......
【解决方案2】:

您不必在 OnTimer 期间禁用计时器,因为您在调用周围有锁,因此所有线程都在等待第一个线程完成...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-07
    • 1970-01-01
    • 1970-01-01
    • 2017-10-27
    • 2014-04-06
    • 2011-07-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多