📜 ⬆️ ⬇️

UDP and C # async / await

Recently there was a need to solve the following simple task: there are several dozen devices (training complexes) that need to be regularly asked for their current status. The complexes communicate via the UDP protocol, and I wanted to do so in order not to think about the survey cycle and determine from which device the answer came, but simply send a request - and when the result came - write it down. I solved this problem before, but I wanted to see how the concept of async / await simplifies and shortens the code. It turned out that the final result takes less than a page.



All polling logic consists of only two methods - the UDP socket reading cycle and the method of sending a command to the device.
')
When we send a command, there are two things that need to be taken into account - this is 1) after sending the command, we need to wait for a response from the device and 2) the answer may not come - then we need to return an exception that will tell us about the timeout.

The asynchronous method of sending a command is as follows (* see Update 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); } } 

Here _client is the standard UdpClient.
We send a command and await to wait for the result, which Task should return to us, saved in the dictionary with the key of our connection (we are waiting for the answer from him). When reading begins, we put the TaskCompletionSource in the dictionary, when we get the answer and the connection is no longer needed, or when an exception is thrown out, we delete it from the dictionary.

Dictionary itself (ConcurrentDictionary is used instead of Dictionary in order to avoid problems with cross-connect calls):

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


There is a moment that deserves attention - this is the extension method WithCancellation (token). It is needed to support the cancellation of the operation using the CancellationToken, and cancels the task, returning an exception if the specified timeout is exceeded.

 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; } } 

And here is the reading cycle itself: we read, until the force is enough, and if the incoming datagram has a connection address, the key with the parameters of which we have already entered into the dictionary, the result is placed in the TaskCompletionSource by this key, and we go back to the method of sending the message to await tcs.Task, only already having the desired result from the device, will return this result to the place of the call.

  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) { ;//     } } }); 

The result pleases. So, async-await simplified the task of polling multiple devices using the UDP protocol.

Update 1
As was rightly noted in the comments, the SendReceiveUdpAsync method needs to be wrapped in try {} finally {}, so that if the task is canceled and the exception is thrown, the value is deleted from the dictionary.

Update 2
Use Reactive Extensions for the same task.
habrahabr.ru/post/238445

Source: https://habr.com/ru/post/238377/


All Articles