Home > Software design >  What should be in the end of await-async chain with busy wait/CPU bound code?
What should be in the end of await-async chain with busy wait/CPU bound code?

Time:09-16

Let's say I have this sample code

var t = Float();
...
t.Wait();


async Task Float()
{
    while (LoopIsAllowed)
    {
        TryToComplete();
        System.Threading.Thread.Sleep(500);
    }
}

In this case code run synchronously and I have warning "async method lacks 'await'". I really need to run Float() async so this warning should almost always be considered. As I see it the problem here is that if I call Float() and await it latter the application will not work as intended and it will stuck on var t = Float() instead of t.Wait()

But I do not see any way to fix that other than like this:

async Task Float()
{
    while (LoopIsAllowed)
    {
        TryToComplete();
        await Task.Run(() => System.Threading.Thread.Sleep(500));
    }
}

Does this fix have any sense? In terms of memory and processor resources is it fine to call Task.Run or is there better way? As I see it after the fix calling var t = Float() will force the code to run synchronously until await Task.Run is reached. Then parent code will continue execution until t.Wait(). Only then it will continue to iterate through while (LoopIsAllowed) loop. And this behavior is exactly what I need.

Is this correct?

EDIT: What should I do if I do not have any delay in my code at all? And there is no other place to await. Should I just add delay? Or should I analyze code and add Task.Run somewhere around time consuming calculations blocks?

CodePudding user response:

Never use Thread.Sleep in async method.

use await Task.Delay(); instead of Thread.Sleep

async Task Float()
{
    while (LoopIsAllowed)
    {
        TryToComplete();
        await Task.Delay(500);
    }
}

and in main method use GetAwaiter().GetResult() instead of wait

var t = Float().GetAwaiter().GetResult();

CodePudding user response:

If your intention is to introduce parallelism, the best place to offload synchronous work to the ThreadPool by using the Task.Run method is the upper level possible. This means that hiding Task.Runs deep inside method chains should generally be avoided. The reasoning is explained in this article: Should I expose asynchronous wrappers for synchronous methods? In your case the (not aptly named) Float method does synchronous work, so it should be a synchronous method with a void return type. You could then offload it to the ThreadPool like this:

Task t = Task.Run(() => Float());
//...
t.Wait();

The Task.Run is a clever little method that handles synchronous and asynchronous delegates (delegates with Task return type) equally well. So if you decide later to improve the efficiency (regarding utilization of threads) of the Float method by converting it to a mixed sync-async method, you can do it without changing anything in the calling site (beyond the name of the method that should now have the Async suffix):

async Task FloatAsync()
{
    while (LoopIsAllowed) // Potential code smell here. Is the LoopIsAllowed volatile?
    {
        TryToComplete();
        await Task.Delay(500);
    }
}
Task t = Task.Run(() => FloatAsync());
//...
t.Wait();
  • Related