0

I'm working on a firmware update app and I'm trying to use Async/Await to avoid blocking the UI. The problem I'm facing is that the process of updating firmware requires a series of steps to occur in sequence (which is not asynchronous). Yet it looks like async is still the way to go when a UI is involved to avoid freezing the application. So how do we not block the UI, sequentially complete a long running series of tasks, and not eat up or waste threads?

Here's a non-async example to illustrate the problem:

public class FirmwareUpdateViewModel : ObservableObject
{
   //ctor
   public FirmwareUpdateViewModel()
   {
       // Process should start on page load
       updateFirmware();
   }

   private void updateFirmware()
   {
       //...

       DeviceHandshake();

       SendMessage(Commands.RestartDevice);

       GetFWFileData();

       WaitForDevice(timeout);

       SendMessage(PreliminaryData);

       //...
   }
}

My first attempt was to await Task.Run(() => everything:

public class FirmwareUpdateViewModel : ObservableObject
{
   //ctor
   public FirmwareUpdateViewModel()
   {
       updateFirmwareAsync();
   }

   private async void updateFirmwareAsync()
   {
       //...

       await Task.Run(() => DeviceHandshakeAsync());

       await Task.Run(() => SendMessageAsync(Commands.RestartDevice));

       //...
   }
}

The problem here besides the async void is that because of the viral nature of Async, when I await inside DeviceHandshakeAsync, plus if Task.Run() is running on it's own thread then we are wasting a ton of threads.

I next tried dropping the Task.Run(() => and just awaiting each method, but as far as I know, the sub-methods will return when they encounter an internal await and begin to run the next sub-method before the previous completes. And now I'm trying to run the firmwareUpdate method async but not awaiting the sub-methods in an attempt to keep them synchronous and fight the viral nature, but this blocks the UI:

public class FirmwareUpdateViewModel : ObservableObject
{
   //ctor
   public FirmwareUpdateViewModel()
   {
       Result = new NotifyTaskComplete<bool>(updateFirmwareAsync());
   }

   private async Task<bool> updateFirmwareAsync()
   {
       //...

       DeviceHandshake();

       SendMessage(Commands.RestartDevice);

       //...
   }
}

The problem here is obviously that without awaits we're running synchronously and on the UI thread. Nothing has "clicked" yet for me in understanding the right approach here. How do we do it right?

Edit: I realized I left out a big part of the problem...that the USB is handled by a 3rd party library that is synchronous. So I need to wrap those functions somehow to avoid blocking the UI:

public class FirmwareUpdateViewModel : ObservableObject
{
   //ctor
   public FirmwareUpdateViewModel()
   {
       // Don't block the UI with this
       updateFirmware();
   }

   private void updateFirmware()
   {
       //...
       // Run all non-blocking but sequentially
       DeviceHandshake();

       SendMessage(Commands.RestartDevice);

       GetFWFileData();

       WaitForDevice(timeout);

       SendMessage(PreliminaryData);

       //...
   }

   private void SendMessage(Command)
   {
      // Existing code, can't make async
      USBLibrary.Write(Command);

   }
}

This is why I'm so confused, because async wants to spread but I can't in between the constructor and end library.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
liquidair
  • 177
  • 2
  • 11
  • 2
    `wasting a ton of threads` no, it will be executed on the thread pool, which is a fixed set of threads – Renat Mar 24 '20 at 17:43
  • 5
    "as far as I know, the sub-methods will return when they encounter an internal await and begin to run the next sub-method" - no. That's the heart of your misunderstanding. Any basic intro to async/await should clear this up. – StackOverthrow Mar 24 '20 at 17:49
  • 2
    @Renat Still I/O bound work should not be wrapped in a CPU-bound task just to make it async. The first choice should be to make the I/O itself async, e.g. by wrapping the I/O completion events or completion callbacks via a TaskCompletionSource into an I/O bound task. – ckuri Mar 24 '20 at 17:50
  • 1
    Does this answer your question? [Perform Multiple Async Method Calls Sequentially](https://stackoverflow.com/questions/26997207/perform-multiple-async-method-calls-sequentially) – devNull Mar 24 '20 at 17:55
  • @StackOverthrow: This is what I was referencing under "Calling a method from an async one": https://www.journeyofcode.com/will-block-debunking-asyncawait-pitfalls/ – liquidair Mar 24 '20 at 20:11
  • @devNull: TBH, I'm not sure. It seems to, but what happens when an async method needs to be nested inside the methods shown above? For instance, when we begin sending firmware packets, the firmware method loops reading the FW file, creating a packet, calculating the checksum, sending the data and waiting for the device to say it wants another packet. If we can safely await methods that are awaiting other methods then it does, provided that is a good approach. – liquidair Mar 24 '20 at 20:45
  • @ckuri: If you get the time, would you mind answering? I believe your suggestion is the right approach for this application. Is it as simple as wrapping the library's synch methods calls in between `var tcs = new TaskCompletionSource();` and `tcs.SetResult(true);`, returning `tcs.Task` from the wrapper method, and awaiting the wrapper? – liquidair Mar 25 '20 at 16:33
  • 1
    Yes, that's basically it. You create a TaskCompletionSource `tcs` in your method. The I/O methods you call in your method must have some kind of completion event or callback and when this one is triggered, you call `tcs.TrySetResult`. Your method just returns `tcs.Task`. The outcome is that your method only initiates the I/O and then exits and when you await your method, everything after the await will be asynchronously executed after TrySetResult was called. – ckuri Mar 25 '20 at 17:57

3 Answers3

2

Regarding how many threads are wasted. This code:

private async void updateFirmwareAsync()
{
    //...
    await Task.Run(() => DeviceHandshakeAsync());
    await Task.Run(() => SendMessageAsync(Commands.RestartDevice));
    //...
}

...most probably wastes practically zero threads. Assuming that the methods DeviceHandshakeAsync and SendMessageAsync are asynchronous, in other words assuming that they return a Task, a ThreadPool thread will be employed just for calling these methods and getting the tasks, and then will be immediately released back to the thread pool because there is nothing else for the thread to do. Now a well behaved asynchronous method when invoked doesn't block the caller, and returns an incomplete Task almost immediately. So, unless these methods are not well behaved, a ThreadPool thread will be employed for a time span measured in microseconds.

But what happens if the two methods are synchronous?

private async void updateFirmwareAsync()
{
    //...
    await Task.Run(() => DeviceHandshake());
    await Task.Run(() => SendMessage(Commands.RestartDevice));
    //...
}

This code wastes a single ThreadPool thread. Maybe the same thread, or maybe two different threads that are employed sequentially. This thread has already been created (unless your code has never used the ThreadPool before that point), so saying that the thread is wasted is an overstatement. It would be more precise to say that a single ThreadPool thread will be reserved, and so unavailable for doing other work, for the whole duration of the firmware update. If your program does heavy use of ThreadPool threads, resulting to hiccups caused by ThreadPool starvation, you can preemptively increase the pool of threads by calling the ThreadPool.SetMinThreads method during the app initialization.

Btw here are my arguments about why it's a good idea to use await Task.Run preemptively inside event handlers of UI applications.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Thank you for this. Part of my confusion is that I can't visualize how this all fits together like I can with sync code. For instance, `DeviceHandshake()` should be near immediate as both devices raise HS pin. `SendMessage()` can be longer as they are sending bytes via i2c. But the actual firmware update method is a while loop that has to read out 256 bytes from the file, calculate the checksum, create the packet and send it, then wait for the device to say it's ready again. I thought I read that it's bad for a single thread to be blocked with the whole method. – liquidair Mar 24 '20 at 20:36
  • 1
    Any method of these could potentially use multiple threads internally. You can't be sure without studying their source code, or using tools to monitor the threads used by the current process. Whatever they do, you don't have any control, or any way to change how they work. With my answer I wanted to make clear that using `await Task.Run` has only a negligible overhead, and does not result to more threads being blocked than the alternative of not using it. Actually I advocate using preemptively this method inside async handlers of UI applications, to ensure that the UI will remain responsive. – Theodor Zoulias Mar 24 '20 at 21:14
-1

Two ways to go about this, I think.

1:

private async void updateFirmwareAsync()
{
   await Task.Run(() => 
   { 
       DeviceHandshakeAsync());
       SendMessage(Commands.RestartDevice);
       GetFWFileData();
       WaitForDevice(timeout);
       SendMessage(PreliminaryData);
   });
}

So run them sequentially in a single task, or

2:

private async void updateFirmwareAsync()
{
   await Task.Run(() => DeviceHandshakeAsync())
             .ContinueWith(() => SendMessage(Commands.RestartDevice))
             .ContinueWith(() => GetFWFileData())
             .ContinueWith(() => WaitForDevice(timeout))
             .ContinueWith(() => SendMessage(PreliminaryData))
   });
}

In practice they achieve much the same, though (1) guarantees they all run in the same thread, whereas (2) means they may run in different thread, so is more like your initial multiple await idea.

Jasper Kent
  • 3,546
  • 15
  • 21
  • 2
    Though these are the ways of last resort. First choice would be to make the I/O methods true async, e.g. by leveraging I/O completion events or callbacks and transforming them via a TaskCompletionSource into an I/O-bound task. Wasting a threadpool thread via Task.Run should be the last choice. – ckuri Mar 24 '20 at 17:54
  • Your first suggestion is great, but the second one is really bad. At first mixing `await` with `ContinueWith` is not a good idea. These two ways of implementing continuations have slightly different behavior. You should choose one or the other, and preferably the `await`. The second and bigger problem is with swallowing the exceptions and continuing with the next step even if the previous one has failed. Most probably this is not what the OP wants. – Theodor Zoulias Mar 24 '20 at 18:20
-1

You should chain the function ConfigureAwait:

 public async void FirmwareUpdateViewModel() {
     await updateFirmwareAsync().ConfigureAwait(false);
 }

This will let you call the task asynchronously without waiting for it to finish, which is causing the user interface to stop painting.