簡體   English   中英

是CancellationTokenSource.CancelAfter()是否泄漏?

[英]Is CancellationTokenSource.CancelAfter() leaky?

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);
}

假設我使用此方法為常用的基於TAP的操作提供超時,並使用.CancelAfter()包裝它。 現在假設用戶提供5分鍾(300秒)的超時值,並且每秒調用此操作100次,這些操作在幾毫秒后完成。 在每秒100次呼叫300秒之后,即使任務很久以前成功完成,所有這些操作也將積累30,000個運行計時器。 它們最終都會過去並運行上面的委托,這可能會引發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);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM