タスク並列ライブラリ:Task.TaskFactory.FromAsync タスクの実行を延期するにはどうすればよいですか?
-
26-09-2019 - |
質問
次のようなタスクを返すメソッドがあります。
public static Task<int> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer == null) throw new ArgumentNullException("buffer");
return Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend);
}
タスクへの参照を保持し、後で実行したいと思います。ただし、FromAsync メソッドによって作成されたタスクはすぐに実行されるようです。実行を延期するにはどうすればよいですか?
解決
まず、構文を見ると、実際に BeginSend
自分でメソッドを実行し、それが IAsyncResult
FromAsync の最初のパラメーター。ただし、これは FromAsync のオーバーロードの 1 つにすぎません。探してみると他にもオーバーロードがあり、探しているのは Func<...>
その代わり。残念ながら、これらのオーバーロードもユーザーに代わってメソッドをすぐに呼び出します。これは、内部で実際に起こっていることは、FromAsync が、 TaskCompletionSource<TResult>
.
実行を延期できることを実際に確認できる唯一の方法は、実際にラップすることです。 FromAsync
自分がやらない親タスクに取り組む Start
あなた自身。例えば:
public static Task<int> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer == null) throw new ArgumentNullException("buffer");
return new Task<int>(() =>
{
return Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend).Result;
}
}
これで、呼び出し元は次のようにタスクを取得できるようになります。
Task<int> task = SendAsync(...);
そして彼らが電話するまで task.Start()
仕事は始まらないでしょう。
他のヒント
、あなただけBeginSend()の呼び出し(これはFromAsync()呼び出しが包まれている前の回答で起こっていることであるを作るために、スレッドプール上の不必要なスケジューリング作業を終わる可能性)別のタスクによるます。
あなたがインターフェイスを変更することができれば、代わりに、あなたのような標準の遅延実行の技術を使用することができます:
public static Func<Task<int>> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer == null) throw new ArgumentNullException("buffer");
return () =>
Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend);
}
は、発信者(すなわちコールFromAsync / BeginSend)動作を開始する結果を呼び出し、結果を処理するためContinueWith()を使用します。
Func<Task<int>> sendAsync = socket.SendAsync(buffer, offset, count);
sendAsync().ContinueWith(
antecedent => Console.WriteLine("Sent " + antecedent.Result + " bytes."));
は適切ではありません、あなたのインターフェイスでのFunc <>を暴露する場合、あなたは別のクラスにそれをラップすることができます:
public class DelayedTask<TResult>
{
private readonly Func<Task<TResult>> func;
public DelayedTask(Func<Task<TResult>> func)
{
this.func = func;
}
public Task<TResult> Start()
{
return this.func();
}
}
public static DelayedTask<int> SendAsync(this Socket socket, byte[] buffer, int offset, int count)
{
if (socket == null) throw new ArgumentNullException("socket");
if (buffer == null) throw new ArgumentNullException("buffer");
return new DelayedTask<int>(() =>
Task.Factory.FromAsync<int>(
socket.BeginSend(buffer, offset, count, SocketFlags.None, null, socket),
socket.EndSend));
}
そして、発信者は次のようになります:
DelayedTask<int> task = socket.SendAsync(buffer, offset, count);
task.Start().ContinueWith(
antecedent => Console.WriteLine("Sent " + antecedent.Result + " bytes."));
ラッピング、他のタスクとタスクの実行を延期することができます。
// Wrap the task
var myTask = new Task<Task<int>>(() => SendAsync(...));
// Defered start
Thread.Sleep(1000);
myTask.Start();
// Thread is not blocked
Console.WriteLine("Started");
// Wait for the result
Console.WriteLine(myTask.Unwrap().Result);