Async WebApi ActionFilterAttribute. An asynchronous module or handler completed while an asynchronous operation was still pending

actionfilterattributeasp.net-web-apiasync-await

I understand await waits for a task (an awaitable) to complete.
But I'm confused about what that actually means.

The code that doesn't work:

public async override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
    if (actionExecutedContext.Response.Content != null)
    {
        var responseContent = await actionExecutedContext.Response.Content.ReadAsStringAsync();
        DoSomething(responseContent);
    }
}

The code that does work:

public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
    if (actionExecutedContext.Response.Content != null)
    {
        var responseContent = actionExecutedContext.Response.Content.ReadAsStringAsync().ContinueWith(
        task =>
        {
            DoSomething(task.Result);
        });
    }
}

Obviously the error message An asynchronous module or handler completed while an asynchronous operation was still pending. tells me that there was no waiting for the async call to complete but instead the "main" thread continued. I expected the thread to continue but not within the current method. I thought the thread would return to the asp.net stack do some other work and return once the await asyncOperation() operation completed.

I'm using await in other places too – (e.g. waiting for web service responses) – and I didn't run into similar problems anywhere. I wonder why the IActionFilterAttribute behaves differently. In fact my web service calls probably take way longer than reading the content of the response into a string.

Can someone please enlighten me? I have the feeling I didn't understand the concept.

Best Answer

Adding async code to a method that returns void is dangerous and almost never what you actually want to do. See What's the difference between returning void and returning a Task?.

Instead, you need to override/implement a method that returns a task. In this case, ActionFilterAttribute hides the Task that IHttpActionFilter provides, so you'll need to implement IActionFilter (ExecuteActionFilterAsync) instead. If you want to use you code as an attribute, just make sure you also derive from the Attribute class.

For example:

public class AsyncActionFilterAttribute : Attribute, IActionFilter
{
    public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
    {
        HttpResponseMessage response = await continuation();
        DoSomething(response);
        return response;
    }
}