Home > Software engineering >  Best practice for async functions with only synchronous contents?
Best practice for async functions with only synchronous contents?

Time:04-08

Say I have a virtual member function that would be called asynchronously:

class Command {
    public abstract Task<object> Execute();  // Meant to be async for implementations
}
class Caller {
    Command command;
    public async Task Call() {
        await command.Execute();  // Command.Execute is always called with `await`
    }
}

For most of the cases, the implementation is just a normal async logic:

class NormalCase : Command {
    public override async Task<object> Execute() {
        await ...;  // Some async works
        return ...;
    }
}

But there are indeed some cases where the derived implementation is just meant to be done synchronously -- i.e. no await is needed.

class SyncCase : Command {
    public override async Task<object> Execute() => 1;  // Just a synchronous value
}

This of course works, but it will bring up a compilation warning (CS1998). Putting a #pragma option can suppress the warning, but there are implementations in many separate files and I'll have to put #pragmas everywhere. (thanks a lot, Unity!) So let's just put this solution aside.

I've also tried several other possible solutions, but none of them works well:

class Solution1 {
    public override async Task<object> Execute() {
        await Task.Delay(1);  // Just NASTY
        return 1;
    }
}
class Solution2 {
    public override Task<object> Execute() => 1;  // Will cause upstream calls to break
}
class Solution3 {
    public override Task<object> Execute() =>
        new Task<object>(() => 1);  // Same, it breaks upstream logic
}

Given this situation, what is the best practice?

CodePudding user response:

The answer is - don't. You're lying to the consumer of the function. Imagine calling a method you assumed was asynchronous to do something in the background, only to find that your UI froze for 10 seconds.

If, and only if, the result is already available, use Task.FromResult

class StaticCase : Command {
    
    public override Task<object> Execute() => Task.FromResult((object)1); 
}

Otherwise make the method async by using Task.Run() to run the blocking code in a separate thread:

class BlockingCase : Command {
    
    public override async Task<object> Execute() 
    {
        var result=await Task.Run(()=>SomeHeavyWork());
        return result;
    }
}
  • Related