0

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

  1. A non-local network may have a larger ping
  2. 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.

Stepan Zakharov
  • 547
  • 2
  • 11
  • 2
    Have you looked at the [background worker](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=net-7.0) - this was originally designed to be able to run a long running async task (before async/await as a thing) on a separate thread and then call back to the UI thread without blocking. This is available in .NET 2.0 and greater. – Jay Jan 12 '23 at 09:35
  • Is the device exclusive to that client as soon as the client establishes a connection? Or can several clients access the device (not at once, of course)? – Fildor Jan 12 '23 at 10:35
  • @Fildor, Seems like the device is exclusive to a client which established a connection. I've tried to connect to the device using two sockets. Both sockets were created without errors, but when I tried to write to the second socket, the second connection aborts. – Stepan Zakharov Jan 12 '23 at 11:34

1 Answers1

1

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 awaits) 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.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks for help! Am I right that, in the first approach, it is not guaranteed that the access to the connection object is "fair", e.g. a polling thread may use it more often? Does the second approach handle the access fairer? – Stepan Zakharov Jan 12 '23 at 11:58
  • 1
    @StepanZakharov it should be the same as with your current `lock` approach. – Guru Stron Jan 12 '23 at 12:16