Вопрос по .net, c# – Является ли CancellationTokenSource.CancelAfter () негерметичным?

4

ВыпускAsync Targeting Pack побудил меня использоватьILSpy посмотреть на чтоАсинхронный шаблон на основе задач (TAP) там были предоставлены методы расширения (некоторые из которых я уже реализовал самостоятельно для использования в VS2010). Я наткнулся на.CancelAfter(TimeSpan) метод дляCancellationTokenSource (который является методом расширения в Async Targeting Pack для .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 раз в секунду, и все они успешно завершаются через несколько миллисекунд. После 300 секунд из 100 вызовов в секунду от всех этих операций накопилось бы 30 000 запущенных таймеров, даже если задачи были успешно завершены давно. Все они в конечном итоге пройдут и запустят вышеупомянутого делегата, который, вероятно,ObjectDisposedException, так далее.

Разве это не утечка, не масштабируемое поведение? Когда я реализовал тайм-аут, я использовалTask/TaskEx.Delay(TimeSpan, CancellationToken) и когда связанная задача закончилась, я отменяю.Delay() так что таймер будет остановлен и утилизирован (в конце концов, это IDisposable, и он содержит неуправляемые ресурсы). Это очистка чрезмерно усердствует? Является ли стоимость одновременной работы десятков тысяч таймеров (и, возможно, последующие выбросы десятков тысяч обнаруженных исключений) действительно несущественной для производительности среднего приложения? Это накладные расходы и утечка.CancelAfter() почти всегда ничтожна по сравнению с фактической работой, и вообще не должна приниматься во внимание?

В некотором смысле это должно произойти, потому что вы можете использовать один и тот же CTS с несколькими Задачами (и CTS не знает о состоянии своих задач). Кроме того, вы можете использовать существующий CTS с новой задачей во время работы таймера. svick

Ваш Ответ

1   ответ
7

доведите до предела и посмотрите, что произойдет. Я не могу получить рабочий набор более 90 МБ с десятью миллионами таймеров. 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);
    }
}
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded Allon Guralnek

Похожие вопросы