我有一个创建和使用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;
}
}
}
答案 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期间禁用计时器,因为你有一个锁定调用,因此所有线程都在等待第一个完成...