UDPおよびC#async / await

最近、次の単純な問題を解決する必要性が生じました。現在の状態を定期的に確認する必要がある数十のデバイス(トレーニング施設)があります。 複合体はUDPを介して通信するため、ポーリングサイクルを考慮せず、応答がどのデバイスからのものであるかを判断するのではなく、単にリクエストを送信し、結果が出たらそれを書き留めたいと思いました。 以前この問題を解決しましたが、async / awaitの概念がどのようにコードを単純化し、削減するかを見たかったのです。 最終結果は1ページ未満で済むことがわかりました。







すべてのポーリングロジックは、UDPソケット読み取りサイクルとコマンドをデバイスに送信する方法の2つの方法のみで構成されています。



コマンドを送信する場合、2つのことを考慮する必要があります-これは、1)コマンドを送信した後、デバイスからの応答を待つ必要がある、2)応答が来ない可能性がある-タイムアウトについて通知する例外を返す必要がある



コマンドを送信する非同期メソッドは次のとおりです(*更新1を参照)



public async Task<byte[]> SendReceiveAsync(byte[] msg, string ip, int port, int timeOut) { var endPoint = new IPEndPoint(IPAddress.Parse(ip), port); var tcs = new TaskCompletionSource<byte[]>(); try { var tokenSource = new CancellationTokenSource(timeOut); var token = tokenSource.Token; if (!_tcsDictionary.ContainsKey(endPoint)) _tcsDictionary.TryAdd(endPoint, tcs); _client.Send(msg, msg.Length, ip, port); var result = await tcs.Task.WithCancellation(token); return result; } finally { _tcsDictionary.TryRemove(endPoint, out tcs); } }
      
      





ここで、_clientは標準のUdpClientです。

コマンドを送信し、Taskから返される結果を待ちます。接続のキーとともに辞書に保存されます(応答を待っているのは彼からです)。 読み取りが開始されると、TaskCompletionSourceを辞書に入れます。答えが得られ、接続が不要になった場合、または例外がスローされた場合は、辞書から削除します。



辞書自体(辞書の代わりにConcurrentDictionaryを使用して、クロストーク呼び出しの問題を回避します):



 private ConcurrentDictionary<Tuple<string,int>, TaskCompletionSource<byte[]>> _tcsDictionary;
      
      







注意が必要な点があります-これは、WithCancellation(トークン)の拡張メソッドです。 CancellationTokenを使用した操作のキャンセルをサポートし、指定されたタイムアウトを超えたときに例外を返し、タスクをキャンセルするために必要です。



 static class TaskExtension { public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource<bool>(); using (cancellationToken.Register( s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs)) if (task != await Task.WhenAny(task, tcs.Task)) throw new OperationCanceledException(cancellationToken); return await task; } }
      
      





読み取りサイクル自体は次のとおりです。十分な強度が得られるまで読み取ります。受信したデータグラムに接続アドレス、辞書に既に入力したパラメーターを持つキーがある場合、結果はこのキーでTaskCompletionSourceに配置され、待機するメッセージを送信する方法に戻りますtcs.Taskは、手元のデバイスから目的の結果のみを取得し、この結果を呼び出し場所に返します。



  Task.Run(() => { IPEndPoint ipEndPoint = null; while (true) { try { var receivedBytes = _client.Receive(ref ipEndPoint); TaskCompletionSource<byte[]> tcs; if (_tcsDictionary.TryGetValue(ipEndPoint, out tcs)) tcs.SetResult(receivedBytes); } catch (SocketException) { ;//     } } });
      
      





結果は楽しいです。 これが、async-awaitがUDPを使用して複数のデバイスをポーリングするタスクを簡素化した方法です。



アップデート1

コメントで正しく指摘されているように、SendReceiveUdpAsyncメソッドはtry {}最後に{}でラップする必要があります。これにより、タスクがキャンセルされて例外がスローされると、値がディクショナリから削除されます。



更新2

同じタスクにリアクティブエクステンションを使用する

habrahabr.ru/post/238445



All Articles