是CancellationTokenSource.CancelAfter()是否泄漏?

Async Targeting Pack的发布促使我使用ILSpy来了解那里提供了哪些基于任务的异步模式(TAP)扩展方法(其中一些我自己已经实现了在VS2010中使用)。 我偶然发现了.CancelAfter(TimeSpan)方法(这是.NET 4.0异步目标包中的扩展方法,但是是.NET 4.5中的实例方法),并认为这可能是实现超时的好方法对于本机没有超时但支持取消的各种操作。

但是查看Async Targeting Pack中的实现,似乎如果关联的Task完成或被取消,则计时器将继续运行。

 /// Cancels the  after the specified duration. /// The CancellationTokenSource. /// The due time in milliseconds for the source to be canceled. public static void CancelAfter(this CancellationTokenSource source, int dueTime) { if (source == null) { throw new NullReferenceException(); } if (dueTime < -1) { throw new ArgumentOutOfRangeException("dueTime"); } Timer timer = new Timer(delegate(object self) { ((IDisposable)self).Dispose(); try { source.Cancel(); } catch (ObjectDisposedException) { } }); timer.Change(dueTime, -1); } 

假设我使用此方法为常用的基于TAP的操作提供超时,并使用.CancelAfter()包装它。 现在让我们假设用户提供5分钟(300秒)的超时值,并且每秒调用此操作100次,这些操作在几毫秒后完成。 在每秒100次呼叫300秒之后,即使任务很久以前成功完成,所有这些操作也将积累30,000个运行计时器。 它们最终都会过去并运行上面的委托,这可能会引发ObjectDisposedException等。

这不是一种漏洞,不可扩展的行为吗? 当我实现超时时,我使用了Task/TaskEx.Delay(TimeSpan, CancellationToken) ,当关联的任务结束时,我取消.Delay()以便定时器停止并处理掉(毕竟它是IDisposable,它确实包含非托管资源)。 这种清理过于热心吗? 让成千上万的定时器同时运行(并且可能在以后抛出成千上万个exception)的成本对于普通应用程序的性能来说真的无关紧要吗? 与.CancelAfter()的实际工作相比, .CancelAfter()的开销和泄漏几乎总是微不足道的,通常应该被忽视吗?

试试吧,把它推到极限,看看会发生什么。 我无法使用1000万计时器超过90 MB。 System.Threading.Timer非常便宜。

 using System; using System.Threading; class Program { public static int CancelCount; static void Main(string[] args) { int count = 1000 * 1000 * 10; for (int ix = 0; ix < count; ++ix) { var token = new CancellationTokenSource(); token.CancelAfter(500); } while (CancelCount < count) { Thread.Sleep(100); Console.WriteLine(CancelCount); } Console.WriteLine("done"); Console.ReadLine(); } } static class Extensions { public static void CancelAfter(this CancellationTokenSource source, int dueTime) { Timer timer = new Timer(delegate(object self) { Interlocked.Increment(ref Program.CancelCount); ((IDisposable)self).Dispose(); try { source.Cancel(); } catch (ObjectDisposedException) { } }); timer.Change(dueTime, -1); } }