知っている限り、ある時点で、そのSetResult
name__またはSetException
name__メソッドが呼び出されて、Task
name__プロパティを介して公開された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
がなかった場合-しかし、doはTask.Factory.StartNew
があります。
質問:
directlyをTaskCompletionSource
name__に関連し、Task.Factory.StartNew
を持たない仮説状況に関連しないシナリオを誰かが例で説明できますか?
イベントベースの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
キーワードと一緒に使用すると特に便利です。
私の経験では、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;
}
socket
をBeginAccept
メソッドに渡すと、ローカルパラメーターを引き上げる必要がないため、コンパイラーのパフォーマンスがわずかに向上します。
それからすべての美しさ:
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();
私にとって、TaskCompletionSource
を使用する古典的なシナリオは、私のメソッドが必然的に時間のかかる操作をする必要がない可能性がある場合です。私たちができることは、新しいスレッドを使用したい特定のケースを選択することです。
これの良い例は、キャッシュを使用する場合です。 GetResourceAsync
メソッドを使用できます。このメソッドは、要求されたリソースをキャッシュで検索し、リソースが見つかった場合(TaskCompletionSource
を使用して新しいスレッドを使用せずに)すぐに戻ります。リソースが見つからなかった場合にのみ、新しいスレッドを使用し、Task.Run()
を使用して取得します。
コード例は次のとおりです。 タスクを使用して条件付きでコードを非同期に実行する方法
このブログ投稿 では、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");
TaskCompletionSourceは作成に使用されますTaskコードを実行しないオブジェクト。現実のシナリオではTaskCompletionSourceはI/Oバウンド操作に最適です。このようにして、操作の継続中にスレッドをブロックすることなく、タスクのすべての利点(戻り値、継続など)を取得できます。 「関数」がIOバウンド操作である場合、新しいTaskを使用してスレッドをブロックすることは推奨されません。代わりにTaskCompletionSourceを使用して、スレーブタスクを作成して、I/Oバウンド操作が終了または失敗したことを示すことができます。
誰も言及していないように見えますが、ユニットテストも考慮できると思います実生活十分です。
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オブジェクト」の別のケースのようです。
これにはきちんとした説明のある実世界の例があります 「。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();
});
}
TaskCompletionSource
を使用した実際のシナリオは、ダウンロードキューを実装するときです。私の場合、ユーザーが100回のダウンロードを開始した場合、一度にすべてをダウンロードしたくないので、ストラテジドタスクを返す代わりに、TaskCompletionSource
にアタッチされたタスクを返します。ダウンロードが完了すると、キューで動作しているスレッドがタスクを完了します。
ここでの重要な概念は、クライアントが実際に開始されたときからタスクの開始を要求するとき、私は分離しているということです。この場合、クライアントがリソース管理を処理する必要がないためです。
c#5コンパイラ(VS 2012+)を使用している限り、.net 4でasync/awaitを使用できることに注意してください。詳細については here を参照してください。
これは物事を単純化しすぎるかもしれませんが、TaskCompletionソースはイベントを待つことを許可します。 tcs.SetResultはイベントが発生した後にのみ設定されるため、呼び出し元はタスクを待つことができます。
詳細については、このビデオをご覧ください。
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.
}