Async Targeting Pack的发布促使我使用ILSpy来了解那里提供了哪些Task-based Asynchronous Pattern (TAP)扩展方法(其中一些我已经实现了自己的方法,可在VS2010中使用)。我偶然发现了.CancelAfter(TimeSpan) CancellationTokenSource 方法(这是.NET 4.0的异步定位包中的扩展方法,但在.NET 4.5中是实例方法),并认为这可能是对各种操作实现超时的好方法 native 没有超时,但支持取消。

但是查看异步目标包中的实现,似乎如果关联的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);
}

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

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

最佳答案

只需尝试一下,将其推到极限,看看会发生什么。我无法通过一千万个计时器将工作量超过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);
    }
}

关于c# - CancellationTokenSource.CancelAfter()是否泄漏?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/10715688/

10-10 23:27