1

次のような単純な機能があります。

    static Task<A> Peirce<A, B>(Func<Func<A, Task<B>>, Task<A>> a)
    {
        var aa = new TaskCompletionSource<A>();
        var tt = new Task<A>(() => 
            a(b =>
            {
                aa.SetResult(b);
                return new TaskCompletionSource<B>().Task;
            }).Result
        );
        tt.Start();
        return Task.WhenAny(aa.Task, tt).Result;
    }

アイデアは単純です。 の実装ではa、 a を返さなければなりTask<A>ません。この目的のために、( type のFunc<A, Task<B>) パラメータを使用する場合と使用しない場合があります。その場合、コールバックが呼び出され、 の結果が設定され、aa完了aa.Taskします。それ以外の場合、 の結果はaそのパラメーターに依存しないため、単純にその値を返します。いずれの状況でも、aa.Taskまたは の結果aは完了するため、 do がそのパラメーターとブロックを使用しない限り、またはブロックによって返されるタスクを使用しない限り、決してブロックされるべきではありませんa

上記のコードは機能します。たとえば、

    static void Main(string[] args)
    {
        Func<Func<int, Task<int>>, Task<int>> t = a =>
        {
            return Task.FromResult(a(20).Result + 10);
        };
        Console.WriteLine(Peirce(t).Result); // output 20
        t = a => Task.FromResult(10);
        Console.WriteLine(Peirce(t).Result); // output 10
    }

ここでの問題は、2 つのタスクaa.Taskttの結果が決定されたらクリーンアップする必要があることWhenAnyです。そうしないと、ハングしたタスクのリークが発生するのではないかと心配しています。これを行う方法がわかりません。誰か何か提案できますか?それとも、これは実際には問題ではなく、C# で解決してくれるのでしょうか?

PS 名前は、命題論理でPeirce有名な「パースの法則」( ) に由来します。((A->B)->A)->A

更新:問題のポイントは、タスクを「破棄」することではなく、実行を停止することです。「メイン」ロジックを 1000 ループに入れると、実行速度が遅くなり (約 1 ループ/秒)、多くのスレッドが作成されるため、問題を解決することがテストされました。

4

3 に答える 3

3

ATaskは管理対象オブジェクトです。Taskアンマネージ リソースを導入しない限り、リソースのリークについて心配する必要はありません。GC にクリーンアップさせ、ファイナライザーにWaitHandle.

編集:

タスクをキャンセルする場合は、 の形式で協調キャンセルを使用することを検討してCancellationTokenSourceください。オーバーロードを介してこのトークンを任意のタスクに渡すことができ、各タスク内に次のようなコードを含めることができます。

while (someCondition)
{
    if (cancelToken.IsCancellationRequested)
        break;
}

そうすれば、例外をスローすることなく、タスクを適切にクリーンアップできます。ただしOperationCancelledException、 を呼び出すと、 を伝播できますcancelToken.ThrowIfCancellationRequested()。したがって、あなたの場合のアイデアは、最初に終了したものが他のタスクにキャンセルを発行して、他のタスクがハングアップしないようにすることです。

于 2012-06-07T04:55:44.253 に答える
1

@Bryan Crosby の回答のおかげで、関数を次のように実装できるようになりました。

    private class CanceledTaskCache<A>
    {
        public static Task<A> Instance;
    }

    private static Task<A> GetCanceledTask<A>()
    {
        if (CanceledTaskCache<A>.Instance == null)
        {
            var aa = new TaskCompletionSource<A>();
            aa.SetCanceled();
            CanceledTaskCache<A>.Instance = aa.Task;
        }
        return CanceledTaskCache<A>.Instance;
    }

    static Task<A> Peirce<A, B>(Func<Func<A, Task<B>>, Task<A>> a)
    {
        var aa = new TaskCompletionSource<A>();
        Func<A, Task<B>> cb = b =>
        {
            aa.SetResult(b);
            return GetCanceledTask<B>();
        };
        return Task.WhenAny(aa.Task, a(cb)).Unwrap();
    }

そしてそれはかなりうまくいきます:

    static void Main(string[] args)
    {
        for (int i = 0; i < 1000; ++i)
        {
            Func<Func<int, Task<String>>, Task<int>> t = 
                async a => (await a(20)).Length + 10;
            Console.WriteLine(Peirce(t).Result); // output 20
            t = async a => 10;
            Console.WriteLine(Peirce(t).Result); // output 10
        }
    }

今では高速で、多くのリソースを消費しません。async/await キーワードを使用しないと、さらに高速になります (私のマシンでは約 70 倍)。

    static void Main(string[] args)
    {
        for (int i = 0; i < 10000; ++i)
        {
            Func<Func<int, Task<String>>, Task<int>> t =
                a => a(20).ContinueWith(ta => 
                    ta.IsCanceled ? GetCanceledTask<int>() : 
                    Task.FromResult(ta.Result.Length + 10)).Unwrap();
            Console.WriteLine(Peirce(t).Result); // output 20
            t = a => Task.FromResult(10);
            Console.WriteLine(Peirce(t).Result); // output 10
        }
    }

ここで問題は、 の戻り値を検出できたとしても、 をスローする以外にブロックa(20)をキャンセルする方法がなく、最適化されないことです。asyncOperationCanceledExceptionWhenAny

更新: コードを最適化し、async/await とネイティブ Task API を比較しました。

更新: 次のコードを記述できれば理想的です。

static Task<A> Peirce<A, B>(Func<Func<A, Task<B>>, Task<A>> a)
{
    var aa = new TaskCompletionSource<A>();
    return await? a(async b => {
        aa.SetResult(b);
        await break;
    }) : await aa.Task;
}

ここで、 a が成功した場合await? a : bは valueaの結果を持ち、 a がキャンセルされた場合は値 b を持ちます ( のようa ? b : cに、 の結果の値aは b と同じ型を持つ必要があります)。 await break現在の非同期ブロックをキャンセルします。

于 2012-06-08T03:33:13.930 に答える
0

MS Parallel Programming Team の Stephen Toub は次のように述べて います。

tldr: ほとんどの場合、タスクを破棄しても何も起こりません。タスクが実際にアンマネージ リソースを割り当てた場合、ファイナライザーはタスク オブジェクトが収集されたときにそれらを解放します。

于 2012-06-07T04:50:06.557 に答える