5

Async Targeting Packのリリースにより、ILSpyを使用して、そこに提供されているタスクベースの非同期パターン (TAP)拡張メソッドを確認するようになりました (そのうちのいくつかは、VS2010 で使用するために独自に実装済みです)。.CancelAfter(TimeSpan)メソッド for (.NET 4.0 の Async Targeting Pack の拡張メソッドですが、.NET 4.5 のインスタンス メソッドです)を偶然見つけてCancellationTokenSource、タイムアウトを実行しないさまざまな操作のタイムアウトを実装する良い方法であると考えました。ネイティブにタイムアウトがありますが、キャンセルをサポートしています。

しかし、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 秒) のタイムアウト値を指定し、この操作を 1 秒間に 100 回呼び出し、数ミリ秒後にすべて正常に完了するとします。1 秒あたり 100 回の呼び出しが 300 秒間続いた後、タスクがずっと前に正常に完了していたとしても、これらすべての操作から 30,000 の実行中のタイマーが蓄積されます。それらはすべて最終的に経過し、上記のデリゲートを実行します。これはおそらくObjectDisposedExceptionなどをスローします。

これは、やや漏れやすく、スケーラブルでない動作ではありませんか? タイムアウトを実装したときに使用Task/TaskEx.Delay(TimeSpan, CancellationToken)し、関連付けられたタスクが終了したら.Delay()、タイマーを停止して破棄するようにキャンセルしました (これは結局のところ IDisposable であり、管理されていないリソースが含まれています)。このクリーンアップは熱心すぎますか? 平均的なアプリケーションのパフォーマンスにとって、何万ものタイマーを同時に実行する (そして、キャッチされた何万もの例外を後でスローする可能性がある) コストは本当に重要ではないのでしょうか? .CancelAfter()ほとんどの場合、オーバーヘッドとリークは、実際に行われている作業と比較してごくわずかであり、一般的に無視する必要がありますか?

4

1 に答える 1

8

試してみて、限界まで押し上げて、何が起こるか見てみましょう。1000万のタイマーで90MBを超えるワーキングセットを取得できません。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);
    }
}
于 2012-05-23T10:51:09.713 に答える