TaskCompletionSource <T>はいつ使用する必要がありますか?


199

私の知る限り、知っているのは、ある時点で、そのSetResultまたはSetExceptionメソッドが呼び出さTask<T>れて、そのTaskプロパティを介して公開を完了することです。

言い換えれば、それはa Task<TResult>とその完成のプロデューサーとして機能します。

私はここに例を見ました:

Funcを非同期で実行する方法が必要で、その操作を表すタスクがある場合。

public static Task<T> RunAsync<T>(Func<T> function) 
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        {  
            T result = function(); 
            tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

私は持っていなかった場合はどちらが*を使用することができTask.Factory.StartNew-しかし、私はない持っていますTask.Factory.StartNew

質問:

誰かが例で関連するシナリオを説明してくださいすることができ、直接TaskCompletionSource にしていない仮想的な私が持っていないしている状況を Task.Factory.StartNew


5
TaskCompletionSourceは主に、新しいスレッドを作成せずに、イベントベースの非同期APIをTaskでラップするために使用されます。
Arvis

回答:


230

私は主に、イベントベースのAPIのみが使用可能な場合に使用します(たとえば、Windows Phone 8ソケット)。

public Task<Args> SomeApiWrapper()
{
    TaskCompletionSource<Args> tcs = new TaskCompletionSource<Args>(); 

    var obj = new SomeApi();

    // will get raised, when the work is done
    obj.Done += (args) => 
    {
        // this will notify the caller 
        // of the SomeApiWrapper that 
        // the task just completed
        tcs.SetResult(args);
    }

    // start the work
    obj.Do();

    return tcs.Task;
}

したがって、C#5 asyncキーワードと一緒に使用すると特に便利です。


4
ここで何が見えるか言葉で書いてくれますか?SomeApiWrapperパブリッシャーがこのタスクを完了させるイベントを発生させるまで、それはどこかで待機していますか?
Royi Namir 2013

今追加したリンクを確認してください
GameScripting 2013年

6
更新だけで、MicrosoftはMicrosoft.Bcl.AsyncNuGetでパッケージをリリースしました。これにより、async/await.NET 4.0プロジェクトのキーワードが許可されます(VS2012以降を推奨)。
エリック

1
Fran_gg7 @あなたはCancellationTokenを使用し、見ることができましたmsdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspxまたはstackoverflowの上、ここで新たな質問として
GameScripting

1
この実装の問題は、イベントがobj.Doneから解放されないため、メモリリークが発生することです
Walter Vehoeven

78

私の経験でTaskCompletionSourceは、古い非同期パターンを現代のものにラップするのに最適ですasync/awaitパターンに。

私が考えることができる最も有益な例は、を使用する場合Socketです。古いAPMおよびEAPパターンがありawaitable TaskますがTcpListener、その方法とTcpClientません。

私は個人的にNetworkStreamクラスでいくつかの問題を抱えており、生を好みSocketます。私もasync/awaitパターンが好きなので、SocketExtenderいくつかの拡張メソッドを作成する拡張クラスを作成しましたSocket

これらのメソッドはすべて、次のTaskCompletionSource<T>ように非同期呼び出しをラップするために使用します。

    public static Task<Socket> AcceptAsync(this Socket socket)
    {
        if (socket == null)
            throw new ArgumentNullException("socket");

        var tcs = new TaskCompletionSource<Socket>();

        socket.BeginAccept(asyncResult =>
        {
            try
            {
                var s = asyncResult.AsyncState as Socket;
                var client = s.EndAccept(asyncResult);

                tcs.SetResult(client);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }

        }, socket);

        return tcs.Task;
    }

私が合格socketBeginAccept私は、コンパイラは、ローカルパラメータをホイストする必要がないのうちのわずかなパフォーマンスの向上を得るようにすることを方法。

それからすべての美しさ:

 var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610));
 listener.Listen(10);

 var client = await listener.AcceptAsync();

1
なぜTask.Factory.StartNewがここで機能しなかったのですか?
Tola Odejayi 2014

23
@Tolaスレッドプールスレッドで実行される新しいタスクを作成するので、上記のコードは、BeginAcceptによって開始されたI / O完了スレッドを使用します。つまり、新しいスレッドを開始しません。
Frans Bouma 2014

4
ありがとう、@ Frans-Bouma。したがって、TaskCompletionSourceは、Begin ... End ...ステートメントを使用するコードをタスクに変換する便利な方法ですか?
Tola Odejayi 2014年

3
@TolaOdejayi返事が遅くなりましたが、はい、それは私が見つけた主要なユースケースの1つです。このコードの移行には素晴らしい働きをします。
エリック

4
TaskFactory <TResult> .FromAsyncを見て、Begin.. End...ステートメントをラップします。
MicBig

37

私にとって、使用の古典的なシナリオTaskCompletionSourceは、私の方法が必ずしも必要ではない可能性がある場合です時間のかかる操作を実行するがです。これにより、新しいスレッドを使用する特定のケースを選択できます。

この良い例は、キャッシュを使用する場合です。あなたは持つことができますGetResourceAsync(使用して、新しいスレッドを使用せずに、一度に要求されたリソースとリターンのためにキャッシュを調べ方法を、TaskCompletionSourceリソースが見つかった場合)。リソースが見つからなかった場合のみ、新しいスレッドを使用し、を使用してそれを取得しTask.Run()ます。

コード例は次のとおりです。タスクを使用して非同期でコードを条件付きで実行する方法


私はあなたの質問と答えも見ました。(回答への私のコメントを見てください).... :-)そして実際、それは教育的な質問と回答です。
Royi Namir 2013

11
これは、実際にはTCSが必要な状況ではありません。Task.FromResultこれを行うために単に使用できます。もちろん、4.0を使用していてTask.FromResult、TCSを使用する目的がない場合は、独自に作成しますFromResult
サービー2014年

@Servy Task.FromResultは.NET 4.5以降でのみ使用できます。それ以前は、それがこの動作を実現する方法でした。
Adi Lester 2014年

@AdiLesterあなたの答えはを参照していますTask.Run。そして、私の以前のコメントは具体的に.NET 4.0に対処しました。
サービー

@Servyこの回答を読んでいるすべての人が.NET 4.5以降をターゲットにしているわけではありません。これは、OPの質問をするのに役立つ、有効で有効な答えだと思います(ところで、.NET-4.0とタグ付けされています)。どちらにしても、反対投票は少し私には少し思えますが、本当に反対投票に値すると信じているなら、先に進んでください。
Adi Lester

25

、このブログの記事、レヴィBotelhoが使用する方法を説明しTaskCompletionSource、あなたがそれを起動し、その終了を待つことができるようなプロセスのための非同期ラッパーを書くこと。

public static Task RunProcessAsync(string processPath)
{
    var tcs = new TaskCompletionSource<object>();
    var process = new Process
    {
        EnableRaisingEvents = true,
        StartInfo = new ProcessStartInfo(processPath)
        {
            RedirectStandardError = true,
            UseShellExecute = false
        }
    };
    process.Exited += (sender, args) =>
    {
        if (process.ExitCode != 0)
        {
            var errorMessage = process.StandardError.ReadToEnd();
            tcs.SetException(new InvalidOperationException("The process did not exit correctly. " +
                "The corresponding error message was: " + errorMessage));
        }
        else
        {
            tcs.SetResult(null);
        }
        process.Dispose();
    };
    process.Start();
    return tcs.Task;
}

とその使用法

await RunProcessAsync("myexecutable.exe");

14

誰も言及していないように見えますが、ユニットテストも実際の生活と見なすことができると思いますます。

見つけた TaskCompletionSource非同期メソッドで依存関係をからかったときに有用であること。

テスト中の実際のプログラム:

public interface IEntityFacade
{
  Task<Entity> GetByIdAsync(string id);
}

単体テストでは:

// set up mock dependency (here with NSubstitute)

TaskCompletionSource<Entity> queryTaskDriver = new TaskCompletionSource<Entity>();

IEntityFacade entityFacade = Substitute.For<IEntityFacade>();

entityFacade.GetByIdAsync(Arg.Any<string>()).Returns(queryTaskDriver.Task);

// later on, in the "Act" phase

private void When_Task_Completes_Successfully()
{
  queryTaskDriver.SetResult(someExpectedEntity);
  // ...
}

private void When_Task_Gives_Error()
{
  queryTaskDriver.SetException(someExpectedException);
  // ...
}

結局のところ、このTaskCompletionSourceの使用法は、「コードを実行しないTaskオブジェクト」の別のケースのようです。


11

TaskCompletionSourceは、コードを実行しないTaskオブジェクトを作成するために使用されます。実際のシナリオでは、TaskCompletionSourceはI / Oバウンド操作に最適です。このようにして、操作中にスレッドをブロックせずに、タスクのすべての利点(戻り値、継続など)を取得できます。「関数」がI / Oバインド操作の場合、新しいTaskを使用してスレッドをブロックすることはお勧めしません。代わりに、TaskCompletionSourceを使用して、I / Oバウンド操作がいつ終了または失敗したかを示すスレーブタスクを作成できます。


5

この投稿には、「NETによる並列プログラミング」ブログの適切な説明のある実例があります。あなたは本当にそれを読むべきですが、とにかくここに要約があります。

ブログの投稿には、次の2つの実装が示されています。

「「遅延」タスクを作成するためのファクトリメソッド。ユーザーが指定したタイムアウトが発生するまで実際にはスケジュールされません。」

示されている最初の実装Task<>は、2つの主要な欠陥に基づいており、そこにあります。2番目の実装の投稿では、を使用してこれらを軽減していTaskCompletionSource<>ます。

これが2番目の実装です。

public static Task StartNewDelayed(int millisecondsDelay, Action action)
{
    // Validate arguments
    if (millisecondsDelay < 0)
        throw new ArgumentOutOfRangeException("millisecondsDelay");
    if (action == null) throw new ArgumentNullException("action");

    // Create a trigger used to start the task
    var tcs = new TaskCompletionSource<object>();

    // Start a timer that will trigger it
    var timer = new Timer(
        _ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite);

    // Create and return a task that will be scheduled when the trigger fires.
    return tcs.Task.ContinueWith(_ =>
    {
        timer.Dispose();
        action();
    });
}

tcs.Taskでawaitを使用し、その後action()を使用することをお勧めします
Royi Namir

5
あなたが去ったコンテキストに戻ってきたので、Continuewithはコンテキストを保持しません。(デフォルトではありません)また、action()の次のステートメントが例外を発生させる場合、awaitを使用すると通常の例外として表示されるので、それをキャッチするのは困難です。
Royi Namir 2014

3
なぜかawait Task.Delay(millisecondsDelay); action(); return;(.Net 4.0では)return Task.Delay(millisecondsDelay).ContinueWith( _ => action() );
sgnsajgon

@sgnsajgonは確かに読みやすく、保守も簡単です
JwJosefy 2014年

@JwJosefy実際、Task.Delayメソッドは、上記のコードと同様に、TaskCompletionSourceを使用して実装できます。実際の実装はここにあります:Task.cs
sgnsajgon

4

これは単純化しすぎているかもしれませんが、TaskCompletionソースを使用すると、イベントを待つことができます。tcs.SetResultはイベントが発生したときにのみ設定されるため、呼び出し元はタスクを待機できます。

詳細については、このビデオをご覧ください。

http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03-TipsForAsyncThreadsAndDatabinding


1
リンクは時間の経過とともに変化し、この回答を無関係にする可能性があるため、ここに関連するコードまたはドキュメントを配置してください。
rfornal 2015

3

私が使用した現実のシナリオはTaskCompletionSource、ダウンロードキューを実装するときです。私の場合、ユーザーが100ダウンロードを開始した場合、一度にすべてを起動したくないので、段階的なタスクを返す代わりに、にアタッチされたタスクを返しますTaskCompletionSource。ダウンロードが完了すると、キューを処理しているスレッドがタスクを完了します。

ここでの重要な概念は、クライアントが実際にタスクが開始されたときからタスクの開始を要求するときに分離するということです。この場合、クライアントにリソース管理を行わせたくないので。

C#5コンパイラ(VS 2012+)を使用している限り、.net 4でasync / awaitを使用できます。詳細については、こちらを参照してください。


0

TaskCompletionSourceキャンセルされるまでタスクを実行してきました。この場合、アプリケーションが実行されている限り、通常実行したいのはServiceBusサブスクライバーです。

public async Task RunUntilCancellation(
    CancellationToken cancellationToken,
    Func<Task> onCancel)
{
    var doneReceiving = new TaskCompletionSource<bool>();

    cancellationToken.Register(
        async () =>
        {
            await onCancel();
            doneReceiving.SetResult(true); // Signal to quit message listener
        });

    await doneReceiving.Task.ConfigureAwait(false); // Listen until quit signal is received.
}

1
使用する必要はありません「非同期」と「TaskCompletionSource」それはすでにタスク作成したように
Mandeep Janjua

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.