0

Suppose I am working on a C# library that is utilizing an interface with the following signature, say:

public interface IMyInterface
{
    public void DoWork();
}

And I have 2 different classes implementing this interface.

  • One that is only running synchronous code (so the void return type would be fine).
  • One that needs to await an async call (so, ideally, I'd want DoWork to be async Task, not void).

The non-async class isn't a problem since it truly does only need a void return type:

public class NonAsyncClass : IMyInterface
{
    public void DoWork()
    {
        Console.WriteLine("This is a non-Async method");
    }
}

But I am trying to think of the best way to implement the async class. I am thinking of something like this:

public class AsyncClass : IMyInterface
{
    public void DoWork()
    {
        var t = Task.Run(async () => {
            await ExternalProcess();
        });

        t.Wait();
    }
}

But it feels like it isn't the right way to do things.

What would be the best way to program against an interface (supposing you don't have control over changing any of the interface signatures) with a void return type when the method needs to be async? Or vice-versa, how does one program against a asyc Task return type when the method doesn't have any asyncronous code?

John Bustos
  • 19,036
  • 17
  • 89
  • 151
  • 2
    If you have `Task` return type without any async code you can return `Task.CompletedTask` at the end of your method (or if you have `Task` you can return `Task.FromResult(T result)`). But if you have `void` method and you want to do something async in it, you have to make your code synchronous, like you did it with `Wait` – stex43 Sep 29 '21 at 04:07
  • 1
    One thing I'm wondering: do you actually want to use `Task.Run` or do you want to just do something like `ExternalProcess().Result;`? – ProgrammingLlama Sep 29 '21 at 04:07
  • So, @stex43, the code I wrote with the `Wait` was actually the correct way, then given he interface signature? – John Bustos Sep 29 '21 at 04:12
  • @Llama, this is more of a conceptual question - I truly just didn't know how to deal with an interface giving a `void` signature when I have an async call I have to make. – John Bustos Sep 29 '21 at 04:14
  • 2
    You should avoid a `Wait` or `GetAwaiter().GetResult()` as often as you can. The problem is that it can cause a deadlock if any sort of synchronization is used in the caller code e.g. the Main-Thread calls that method and the code in `Task.Run` needs to synchronize to main due to update the UI. The Main-Thread can`t invoke that call because it`s waiting for the end of the Task -> deadlock. Try to design the method as `Task DoWorkAsync` and return `Taks.CompletedTask` in case of an synchronous implementation. – Sebastian Schumann Sep 29 '21 at 04:18
  • Given the fact that I have to program against this interface, @SebastianSchumann, how would I do that? Could you post it as an answer, perhaps? I'm just still lost about what to do when you have an interface with a `void` signature and I do have to await a task in the implementation method. – John Bustos Sep 29 '21 at 04:20
  • 1
    @JohnBustos you can't do anything else, if you have to deal with `void` signature, but I agree with @sebastian-schumann that it's not good at all. :) – stex43 Sep 29 '21 at 04:22
  • 1
    If you've to use the interface as it is you've two options: 1. The caller doesn't need to know that there is something going on in parallel. Just use `Task.Run` without waiting for the end of the task. 2. The asynchronous code does something that needs to be finished before the method returns: You have to wait using `Wait` or better [`GetAwaiter().GetResult()`](https://stackoverflow.com/q/36426937). BTW: if you handle events and you have the opportunity to change the interface [Stephen Cleary](https://blog.stephencleary.com/2013/02/async-oop-5-events.html) has a fine solution. – Sebastian Schumann Sep 29 '21 at 04:37

2 Answers2

4

What would be the best way to program against an interface (supposing you don't have control over changing any of the interface signatures) with a void return type when the method needs to be async?

This is essentially the same as asking "what is the best way to block on asynchronous code", and the answer is the same: there is no best way. There are a variety of hacks - as I describe in an MSDN article - but there is no hack that works in every scenario.

The blocking-threadpool-hack (Task.Run(async () => await ExternalProcess()).GetAwaiter().GetResult();) probably works in the most scenarios; it only fails if ExternalProcess has to access something tied to a specific context (e.g., UI elements or HttpContext.Current), or if it depends on the implicit synchronization provided by a one-thread-at-a-time context. So as long as your ExternalProcess doesn't need a specific context and if it's thread-safe, then the blocking thread pool hack should work.

Or vice-versa, how does one program against a asyc Task return type when the method doesn't have any asyncronous code?

This one's easy. An async-compatible (e.g., Task-returning) interface method means that the implementation may be asynchronous, not that it must be asynchronous. Synchronous implementations can use Task.FromResult and Task.FromException to create the returned Task. Alternatively, you can mark the method async and ignore the compiler warning, which is the approach taken by my AsyncEx library.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Why do you ignore the compiler warning over returning `Task.CompletedTask` or `Task.FromResult` in your lib? As far as I can see the method `ExecuteAsTask` will be executed synchronously. There should be no difference between `async` without `await` and `return Task.CompletedTask;` or do I miss some runtime differences? I know that `async` will create a state machine but if the runtime behaviour of the synchronous call to `func` will not change there should be no need for that. I'm sure that I'm missing something. Please explain that to me. – Sebastian Schumann Sep 29 '21 at 14:43
  • @SebastianSchumann: It gets trickier if the synchronous code throws an exception. – Stephen Cleary Sep 29 '21 at 17:58
  • Do you have any blog about that? I'm very interested about this. – Sebastian Schumann Sep 30 '21 at 04:19
  • @SebastianSchumann: I do not, sorry. But it is becoming more common, so it's probably a good idea to write one! – Stephen Cleary Oct 01 '21 at 13:58
  • Oh yeah - please. – Sebastian Schumann Oct 05 '21 at 04:12
  • @StephenCleary I'm reading your cookbook and a question about safe task completion has arisen. Could you take a look at this question, please? https://stackoverflow.com/questions/69543717/why-arent-any-measures-taken-against-instructions-reordering-in-this-working-mu – bimjhi Oct 12 '21 at 16:18
  • @bimjhi: Looks like Raymond beat me to it. :) – Stephen Cleary Oct 13 '21 at 11:08
  • @StephenCleary Have you had time to think about a blog post regarding suppressing the compiler warning? Or do you have another source that illustrates the topic? – Sebastian Schumann Mar 17 '22 at 11:23
  • 1
    @SebastianSchumann: I have not written a post about it, but that's probably a good idea. It does get brought up enough here on SO that a longer explanatory post would be helpful. I have added it to my list of ideas; thanks! – Stephen Cleary Mar 17 '22 at 12:23
-2

If ExternalProcess return as void you cannot use await against void. Complier will tell you when try to complie code. Then problem is not occured because interface.

If ExternalProcess reuturn as "Task" datatype your code must be work fine.

Not clear in your sample code. You want to convert Async Operation to Synchronous? When user call DoWork method , program will be block until operation finish and continue doing next line of code.

Sum Settavut
  • 1
  • 1
  • 1