非同期プログラミングを扱う際のベストプラクティスに従っているかどうかを知りたいのですが。私が手元にある問題はこれです:私は同時に2つのデバイスと話している。SendMessageAsync(msg)
メソッドを使用してメッセージを送信できます。両方のデバイスが同時にこのメッセージを受信しましたが、その見返りとして、一方のデバイスのみが応答を送信し、もう一方のデバイスは単に応答しません。
また、メソッドはCancellationToken
、タイムアウト後またはその他の理由ですべてをキャンセルできるように、を受け入れる必要があります。
だから私はメッセージを読むためにこのメソッドを書きました:
public async Task<Message> GetMessageAsync(CancellationToken token)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token, new CancellationToken(false)))
{
var device1 = Task.Run(async () => { return await GetDevice1Async(cts.Token); });
var device2 = Task.Run(async () => { return await GetDevice2Async(cts.Token); });
var response = await Task.WhenAny(device1, device2);
cts.Cancel(); //Only one device answers, so cancel the other other one
return response.Result;
}
}
私のソリューションがベストプラクティスに従っているのかどうか疑問に思いました。具体的には、非常に優れたパフォーマンスを実現したいと考えています(私が話しているデバイスはUSBデバイスなので、迅速にサービスを提供できるようにしたいと考えています)。そのため、メッセージを読む必要があるたびに2つのタスクを作成することにあまり満足していません。
今のところ、私のソリューションは機能しているようですが、一部のマシンでは実行速度が遅いと報告されています。しかし、私のマシンでは非常に高速に実行されているため、コードの問題によるものなのか、それとも他の問題によるものなのかわかりません。
私はそれを正しくやっていますか?このソリューションを改善する方法はありますか?
編集:Jeroen Mostertとusrからの提案に基づいて、次のようにコードを更新しました。
public async Task<Message> GetMessageAsync(CancellationToken token)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token, new CancellationToken(false)))
{
var taskList = new List<Task<Message>> {
GetDevice1Async(cts.Token),
GetDevice2Async(cts.Token)
};
// wait for any operation to finish, then cancel the other one
var task = await Task.WhenAny(taskList);
cts.Cancel();
//ensure both operations are either finished or cancelled before returning
try {
await Task.WhenAll(taskList);
}
catch (OperationCanceledException)
{
//The exception is expected as is safe to ignore
}
return task.Result;
}
}
基本的に、これは問題ありません。
ただし、問題の1つは、常に1つのタスクを放棄していることです。キャンセルを要求されますが、キャンセルしない場合は引き続き実行されます。これはリソース使用量を蓄積する可能性があり、あなたが見ている遅さを説明するかもしれません。
.NETでは、IOが簡単にキャンセルできることはめったにありません。たとえば、ソケットの場合、ソケットを閉じる以外にIOをキャンセルすることはできません。USBデバイスと通信するときは、キャンセルが実際に機能することを確認する必要があります。
別の問題はcts
、トークンが最後に使用された後に破棄される可能性があることです。呼び出した時点で、トークンに登録さcts.Dispose()
れる可能性のあるタスクがまだ1つ実行されています。それが機能することが保証されているかどうかはわかりません。
キャンセルされた操作が実際にキャンセルされるのを待つことで、これを修正できます。
cts.Cancel();
await Task.WhenAll(tasks); //Maybe need to swallow exceptions.
return response.Result;
Task.Run(async
通常のメソッド呼び出しだけに単純化できる可能性があります。これにより、セマンティクスが少し変わります。同期コンテキストを通過させ、asyncメソッドの一部を同期的に実行します。あなたはそれを望むかもしれないし、望まないかもしれません。これは重大な効率の問題ではありません。私の主観的な評価は、このTask.Run
パターンがコードを明確にし、コードを正しく理解しやすくすることです。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加