web-dev-qa-db-ja.com

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

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

つまり、Task<TResult>およびその完了のプロデューサーとして機能します。

here の例を見ました:

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がなかった場合-しかし、doTask.Factory.StartNewがあります。

質問:

directlyTaskCompletionSourcename__に関連し、Task.Factory.StartNewを持たない仮説状況に関連しないシナリオを誰かが例で説明できますか?

180
Royi Namir

イベントベースのAPIのみが利用可能な場合に主に使用します( たとえば、windows phone 8 sockets ):

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キーワードと一緒に使用すると特に便利です。

211
GameScripting

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

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

個人的に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();
72
Erik

私にとって、TaskCompletionSourceを使用する古典的なシナリオは、私のメソッドが必然的に時間のかかる操作をする必要がない可能性がある場合です。私たちができることは、新しいスレッドを使用したい特定のケースを選択することです。

これの良い例は、キャッシュを使用する場合です。 GetResourceAsyncメソッドを使用できます。このメソッドは、要求されたリソースをキャッシュで検索し、リソースが見つかった場合(TaskCompletionSourceを使用して新しいスレッドを使用せずに)すぐに戻ります。リソースが見つからなかった場合にのみ、新しいスレッドを使用し、Task.Run()を使用して取得します。

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

34
Adi Lester

このブログ投稿 では、Levi 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");
23
Sarin

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

11
v1p3r

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

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オブジェクト」の別のケースのようです。

10
superjos

これにはきちんとした説明のある実世界の例があります 「。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();
    });
}
5
urig

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

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

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

3
Yaur

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

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

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

3
nmishr

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.
}
0
Johan Gov