The release of the Async Targeting Pack prompted me to use ILSpy to see what methods of expanding the task-based asynchronous template (TAP) (some of which I already have implemented by myself for use in VS2010). I came across the .CancelAfter(TimeSpan)
Method of the CancellationTokenSource
(which is like an extension method in Async Targeting Pack for .NET 4.0, but is an instance method in .NET 4.5) and thought that this might be a good way to implement a timeout for various operations. which do not have a timeout but support cancellation.
But looking at the implementation in the Async Targeting Pack, it seems that if the associated Task
completes or is canceled, the timer continues to work.
/// <summary>Cancels the <see cref="T:System.Threading.CancellationTokenSource" /> after the specified duration.</summary> /// <param name="source">The CancellationTokenSource.</param> /// <param name="dueTime">The due time in milliseconds for the source to be canceled.</param> 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); }
Let's say I use this method to provide a timeout for a commonly used TAP-based operation and complete it with .CancelAfter()
. Now suppose the user provides a timeout value of 5 minutes (300 seconds) and calls this operation 100 times per second, and they all complete successfully in a few milliseconds. After 300 seconds, 100 calls per second, 30,000 timers will accumulate from all these operations, although the tasks have been completed successfully for a long time. In the end, they will all go through and throw the above delegate, which will probably throw an ObjectDisposedException
, etc.
Isn't that something non-local, non-scalable behavior? When I completed the timeout, I used Task/TaskEx.Delay(TimeSpan, CancellationToken)
, and when the related task ended, I canceled .Delay()
so that the timer was stopped and deleted (in the end, it is IDisposable and contains unmanaged resources). Is this cleaning overly diligent? Is the cost of simultaneously using tens of thousands of timers (and possibly the subsequent throwing of tens of thousands of caught exceptions) really insignificant for performance for an average application? Are the overhead and .CancelAfter()
almost always negligible compared to the actual work being performed and are usually not taken into account?
source share