是CancellationTokenSource.CancelAfter()是否泄漏?

All*_*nek 5 .net c# .net-4.0 task-parallel-library .net-4.5

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

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

/// <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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

Han*_*ant 8

试试吧,把它推到极限,看看会发生什么.我不能让工作集超过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);
    }
}
Run Code Online (Sandbox Code Playgroud)