定时器和IDisposable - Dispose中的额外保护?

时间:2010-11-02 21:25:04

标签: c# .net silverlight timer idisposable

我有一个创建和使用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方法,以防止定时器执行的可能性吗?

现在我考虑一下,如果List在Dispose中不为空,我应该考虑我应该怎么做。在对象的使用模式中应该在那时是空的,但我想任何事情都是可能的。让我们说在调用Dispose时列表中还有剩余的项目,继续尝试处理它们会有好处还是坏处?我想我可以提出这个可靠的旧评论:

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

列表中是否有任何项目是次要的。我真的只想知道处理可能启用的定时器和Dispose的最佳实践。

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

编辑:

我发现了一个非常好的解决方案here。一个答案是jsw,建议用Monitor.TryEnter / Monitor.Exit保护OnTimer事件,有效地将OnTimer代码放在关键部分。

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

对于我的工作,如果至少有一个项目已添加到列表,我只想要启动计时器。所以,首先,我的计时器被禁用。输入Add后,禁用计时器,以便在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;
    }
  }
}

2 个答案:

答案 0 :(得分:1)

我在此代码中看到了一些严重的同步问题。我认为你要解决的是一个经典的读写者问题。使用当前的方法很可能你会遇到一些问题,比如如果有人在处理它时试图修改列表会怎样?

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

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

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

这段代码应该是最简单的(虽然不是最佳的)。还应该在Add方法中添加类似的代码(锁定集合)。 希望它有所帮助,如果没有消息我。 路加

答案 1 :(得分:1)

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