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

2

11

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.

Sentimentality answered 19/3, 2013 at 15:0 Comment(3)
Action filters (part of MVC, not WebAPI) do not support asynchronous operations. If you need an async action filter, try using a message handler instead. Oh, and vote here.Brause
This is a WebAPI question and I'm using the proper ActionFilterAttribute (System.Web.Http...) - are you saying it should work? :)Sentimentality
I see. In that case, you would probably need to define your own AsyncActionFilterAttribute and implement IActionFilter.ExecuteActionFilterAsync.Brause
L
14

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;
    }
}
Lasley answered 12/7, 2013 at 17:43 Comment(0)
P
7

Instead of implementing

public async override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)

you have to implement the async version of OnActionExecuted method as follows:

public override Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)

This way you can use await inside a method and behavior will be as you expected.

Hope this helps.

Propolis answered 28/5, 2015 at 10:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.