TaskCompletionSource
を使用してMyService
をラップしたいのですが、これは単純なサービスです。
public static Task<string> ProcessAsync(MyService service, int parameter)
{
var tcs = new TaskCompletionSource<string>();
//Every time ProccessAsync is called this assigns to Completed!
service.Completed += (sender, e)=>{ tcs.SetResult(e.Result); };
service.RunAsync(parameter);
return tcs.Task;
}
このコードは初めてうまくいきます。しかしsecondを呼び出す時間ProcessAsync
を呼び出すだけで、Completed
のイベントハンドラーが再度割り当てられます(同じservice
変数が毎回使用されます)。したがって、2回実行されます。 2回目はこの例外をスローします。
既に完了している場合、タスクの最終状態の遷移を試みる
tcs
を次のようにクラスレベルの変数として宣言する必要があるかどうかはわかりません。
TaskCompletionSource<string> tcs;
public static Task<string> ProccessAsync(MyService service, int parameter)
{
tcs = new TaskCompletionSource<string>();
service.Completed -= completedHandler;
service.Completed += completedHandler;
return tcs.Task;
}
private void completedHandler(object sender, CustomEventArg e)
{
tcs.SetResult(e.Result);
}
さまざまな戻り値の型で多くのメソッドをラップする必要があるため、失われたコード、変数、イベントハンドラーを記述する必要があるため、これがこのシナリオでのベストプラクティスであるかどうかはわかりません。それで、この仕事をするより良い方法はありますか?
ここでの問題は、各アクションでCompleted
イベントが発生しますが、TaskCompletionSource
は1回しか完了できないことです。
ローカルTaskCompletionSource
を引き続き使用できます(必要があります)。 TaskCompletionSource
を完了する前に、コールバックの登録を解除する必要があります。そうすれば、この特定のTaskCompletionSource
を使用したこの特定のコールバックが再び呼び出されることはありません。
public static Task<string> ProcessAsync(MyService service, int parameter)
{
var tcs = new TaskCompletionSource<string>();
EventHandler<CustomEventArg> callback = null;
callback = (sender, e) =>
{
service.Completed -= callback;
tcs.SetResult(e.Result);
};
service.Completed += callback;
service.RunAsync(parameter);
return tcs.Task;
}
これにより、サービスがこれらすべてのデリゲートへの参照を保持しているときに発生する可能性のあるメモリリークも解決されます。
ただし、これらの操作を複数同時に実行することはできません。少なくとも、リクエストとレスポンスを照合する方法がない限りはそうではありません。
i3arnon の answer の代替ソリューションは次のようになります。
public async static Task<string> ProcessAsync(MyService service, int parameter)
{
var tcs = new TaskCompletionSource<string>();
EventHandler<CustomEventArg> callback =
(s, e) => tcs.SetResult(e.Result);
try
{
contacts.Completed += callback;
contacts.RunAsync(parameter);
return await tcs.Task;
}
finally
{
contacts.Completed -= callback;
}
}
ただし、このソリューションには、コンパイラによって生成されたステートマシンがあります。より多くのメモリとCPUを使用します。
MyService
はCompleted
イベントを複数回発生させるようです。これにより、SetResult
が複数回呼び出され、エラーが発生します。
私には3つの選択肢があります。 Completedイベントを1回だけ発生するように変更します(複数回完了できるのは奇妙に思えます)。SetResult
を TrySetResult
に変更して、例外がスローされないようにします2回目に設定しようとすると(イベントが引き続き呼び出され、完了ソースが設定されようとするため、小さなメモリリークが発生します)、またはイベントのサブスクライブを解除します( i3arnonの回答 )