I'm working on a gui application (C#, .NET Core, WinUI 3) that connects to a remote device. The device acts like a server with a single available connection.
By the app's requirements, I need to periodically poll the device and allow a user to send commands. To solve this,
- I create a single
connection
object (say, a tcp/ip socket) - I start the polling via
pollingTask = Task.Start(polling.PollInfinite)
- In the polling and the user's network operations, the connection singleton is locked (
lock (connection) { connection.send/receive }
).
The scheme above works well when I test it in a local network, so messages to the device does not mess up and the app's UI is not blocked (at least by my experience).
Now I imagine two situations
- A non-local network may have a larger ping
- A user may run the app on a computer with one thread
In each situation... Would the UI blocked? Could the user send commands while polling
waits for the device response?
Also, I'm looking for general recommendations for meeting the app's requirements.
P.S. I'm not experienced with asynchronous programming, but suppose that it should be used anywhere in my app when a network operation happen.
CodePudding user response:
P.S. I'm not experienced with asynchronous programming, but suppose that it should be used anywhere in my app when a network operation happen.
Yes, it was one of the main reasons for introduction of async
-await
into the language.
As for the actual problem there are several approaches to handle this. The most similar one to yours is to switch from lock
(since it can't handle await
s) to SemaphoreSlim
with one slot and use async versions of connection.send/receive
(if they do not exist - wrap them into Task.Run
):
SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); // single instance
// ... somewhere in the code
await _semaphore.WaitAsync();
try
{
await connection.sendAsync/receiveAsync // or await Task.Run(() => connection.send/receive)
}
finally
{
_semaphore.Release();
}
For convenience you can wrap this into proxy for easier consumption.
Another approach is to implement something similar to queued background task in modern ASP.NET Core. Idea in the nutshell is quite simple - you create a dedicated Task
(i.e. thread, see TaskCreationOptions.LongRunning
) which infinitely monitors queue (concurrent, can be ConcurrentQueue<Func<Task<Action>>>
or ConcurrentQueue<Func<Task<Action<YourConnectionObjectType>>>>
) and synchronously processes queued items and add to this queue from other places in the app.