2

We have Web API controllers that look like this:

public class HomeController : ApiController
{
    Logger logger = new Logger();

    [HttpGet, Route("")]
    public IHttpActionResult Index()
    {
        logger.Info("Some info logging");

        return Ok();
    }
}

Internally, our loggers use the HttpClient to POST data to an Azure Event Hub. This means that the logger has a synchronous method which internally calls an asynchronous method.

The broken implementation

When our logger has a private method with an async void signature like this:

public class Logger
{
    public void Info(string message)
    {
        LogAsync(message);
    }

    private async void LogAsync(string message)
    {
        await PostAsync(message);
    }
}

We see the following error:

Yellow screen of death

The working implementation

If we change our logger to use an async Task signature like this it works:

public class Logger
{
    public void Info(string message)
    {
        LogAsync(message);
    }

    private async Task LogAsync(string message)
    {
        await PostAsync(message);
    }
}

The question

In both cases our logging messages are sent to the Event Hub. I'd like to know why async Task allows the controller to return the expected result, and async void results in an InvalidOperationException?

Also, is there any best practice regarding calling into asynchronous code from a synchronous method?

I've posted a sample solution here: https://github.com/kevinkuszyk/async-task-vs-async-void-sample

15
  • 2
    Don't use async void
    – stuartd
    Commented Dec 6, 2016 at 16:54
  • 2
    Why do you have to call it from a synchronous method in the first place? If the underlying operation is asynchronous, make the operation which invokes it async. That's why async exists.
    – David
    Commented Dec 6, 2016 at 16:55
  • 2
    Both of the implementations are broken. One tells you that it's broken so you can fix it, the other one just doesn't work and doesn't tell you that it doesn't work so you don't realize that it's not working.
    – Servy
    Commented Dec 6, 2016 at 16:56
  • 2
    @MichaelCoxon Returning a task that you just then drop on the floor isn't any better though. It's only useful when the consumer actually uses it to determine when the operation finishes.
    – Servy
    Commented Dec 6, 2016 at 17:03
  • 2
    @KevinKuszyk: "It needs to be synchronous so that consuming code can log and move on." - I'm not entirely following the reasoning there. If the operation advertises itself as being synchronous, then consuming code is going to expect to wait for it to complete. Attempting to hide an asynchronous operation sounds like an attempt at a kind of "fire and forget" methodology. But the "forget" part of that methodology means that you don't care if the operation succeeds or not. So why handle errors in the first place?
    – David
    Commented Dec 6, 2016 at 17:04

2 Answers 2

1

Avoiding judgement on the merits of this approach, the answer to your actual question is in the answer to this question.

There are various ways you could fix it while maintaining that usage pattern:

  • Don't await in LogAsync()
  • Use Task.Run(() => LogAsync());
1
  • Thanks @sellotape - the question you link to provides the background information I was missing. The example above is a simplification to repro the issue we have. In our actual implementation the logger writes to a TraceSource and we have a listener plugged in on the other end which sends the messages to the event hub. The contract on TraceListener means our entry point is a synchronous method. As I mentioned above we use HttpClient which is asynchronous to send the messages to Azure, hence the problem. I would also be interested in your thoughts on this approach. Commented Dec 7, 2016 at 8:48
-1

If you call an async method synchronously call it as follows:

async Task MyMethod()
{
   ...
}

public void WaitOnTask()
{
  var resultTask = MyMethod();
  resultTask.GetAwaiter().GetResult();
}

async void should be avoided because there is nothing stopping the method returning immediately and this causes problems when the caller is not expecting the method to return without the asynchronous process completing as is the case in the ASP.NET runtime.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.