Search code examples
c#owinwebapi2self-host-webapi

task returned by IHttpActionResult.ExecuteAsync() overload is not run


I'm trying to create a self-hosted Web service that starts its server with Microsoft.Owin.Hosting.WebApp.Start<NotifyStartup>("http://localhost:9000/") and contains a controller derived from System.Net.Http.ApiController. NotifyStartup looks like this:

using System.Web.Http;
using Owin;
...
class NotifyStartup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        var config = new HttpConfiguration();
        config.MapHttpAttributeRoutes();

        appBuilder.UseWebApi(config);
    }
}

The controller has this URI handler:

using System.Web.Http;
...
[Route("notify")]
[HttpPost]
public IHttpActionResult Notify([FromBody]object body)
{
    return new HttpAction();
}

Here's HttpAction:

using System.Net.Http;
using System.Web.Http;
using System.Threading;
using System.Threading.Tasks;
...
public class HttpAction : IHttpActionResult
{
    public HttpAction() { }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return new Task<HttpResponseMessage>(() =>
        {
            var rspContent = "here's a response string";
            var rsp = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
            if (!string.IsNullOrEmpty(rspContent))
            {
                rsp.Content = new StringContent(rspContent);
            }
            return rsp;
        }, cancellationToken);
    }
}

(At some point HttpAction will pay attention to Notify()'s body parameter and rspContent will be assigned something requiring some database lookups, which is why I'm trying to make this work asynchronously.)

When I run the program and POST to http://localhost:9000/notify/ the URI handler is called, it creates an HttpAction instance and that instance's ExecuteAsync() method is called. However, the task it returns is never run, and the client hangs waiting for a response. If I change ExecuteAsync() so that the work is done synchronously and the response returned in a wrapper task:

var rspContent = "here's a response string";
var rsp = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
if (!string.IsNullOrEmpty(rspContent))
{
    rsp.Content = new StringContent(rspContent);
}
return Task.FromResult(rsp);

that wrapper task is run and the client receives its response.

As far as I know, the tasks created by new Task<>... and Task.FromResult() should look identical to the caller. Why will it await (or whatever it's actually doing to obtain the result) one and not the other? What am I doing wrong? Is it possible to make this work?


Solution

  • the tasks created by new Task<> and Task.FromResult() should look identical to the caller.

    They do look identical from the caller perspective, but they aren't the same from the implementation perspective.

    The Task constructor doesn't start the task, that's why you shouldn't use it. Instead use Task.Run which returns a hot task:

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.Run(() =>
        {
            var rspContent = "here's a response string";
            var rsp = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
            if (!string.IsNullOrEmpty(rspContent))
            {
                rsp.Content = new StringContent(rspContent);
            }
            return rsp;
        }, cancellationToken);
    }
    

    Although I'd argue that this may be redundant by itself, since an action in WebAPI by itself is already ran on a thread pool thread, it is usually redundant to wrap it around inside an additional thread.