Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return from Controller without waiting for async method to finish

I've read myself blue and am hoping there's a simple answer.

I have a web API that handles telemetry from various apps "in the wild" In one of my controllers, I want to receive a request to log an error to my central monitoring database and return a response near immediately as possible (I have no real way of knowing how critical performance might be on the caller's end and there's already a significant hit for making the initial web service request).

Essentially, what I'm looking for is something like this:

public IHttpActionResult Submit() {
    try {
        var model = MyModel.Parse(Request.Content.ReadAsStringAsync().Result);

        // ok, I've got content, now log it but don't wait
        // around to see the results of the logging, just return
        // an Accepted result and begone

        repository.SaveSubmission(model); // <-- fire and forget, don't wait

        return Accepted();

    } catch (Exception)
        return InternalServerError();
    }
}

It seems like it ought to be straightforward, but apparently not. I've read any number of various posts indicating everything from yup, just use Task.Run() to this is a terrible mistake and you can never achieve what you want!

The problem in my scenario appears to be the fact that this process could be terminated mid-process due to it running on the ASP.NET worker process, regardless of the mire of different ways to invoke async methods (I've spend the last two hours or so reading various SO questions and Stephen Cleary blogs... whew).

If the underlying issue in this case is that the method I'd 'fire and forget' is bound to the http context and subject to early termination by the ASP.NET worker process, then my question becomes...

Is there some way to remove this method/task/process from that ASP.NET context? Once that request is parsed into the model, I myself have no more specific need to be operating within the http context. If there's an easy way I can move it out of there (and thus letting the thing run barring a website/apppool restart), that'd be great.

For the sake of due diligence, let's say I get rid of the repository context in the controller and delegate it to some other context:

public IHttpActionResult Submit() {
    try {
        var model = MyModel.Parse(Request.Content.ReadAsStringAsync().Result);

        SomeStaticClass.SaveSubmission(model); // <-- fire and forget, don't wait

        return Accepted();

    } catch (Exception)
        return InternalServerError();
    }
}

... then the only thing that has to "cross lines" is the model itself - no other code logic dependencies.

Granted, I'm probably making a mountain of a molehill - the insertion to the database won't take but a fraction of time anyway... it seems like it should be easy though, and I'm apparently too stubborn to settle for "good enough" tonight.

like image 391
jleach Avatar asked Mar 19 '16 04:03

jleach


People also ask

What happens if an async method is not awaited?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Which is the recommended way to wait for an async method to complete?

No problem, just make a loop and call this function with an await: [code] for (int i = pendingList. Count - 1; i >= 0; i--)

Can we call async method without await?

In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously. Code after each await expression can be thought of as existing in a .then callback.

Can async method have return value?

Async methods can have the following return types: Task, for an async method that performs an operation but returns no value. Task<TResult>, for an async method that returns a value. void , for an event handler.


1 Answers

Ok, found a few more that were actually helpful to my scenario. The basic gist of it seems to be don't do it.

In order to do this correctly, one needs to submit this to a separate component in a distributed architecture (e.g., message or service queue of some sort where it can be picked up separately for processing). This appears to be the only way to break out of the ASP.NET worker process entirely.

One S/O comment (to another S/O post) lead me to two articles I hadn't yet seen before posting: one by Stephen Cleary and another by Phil Haack.

SO post of interest: How to queue background tasks in ASP.NET Web API

Stephen's Fire and Forget on ASP.NET blog post (excellent, wish I had found this first): http://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html

And Phil's article: http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/

The following project by Stephen may be of interest as well: https://github.com/StephenCleary/AspNetBackgroundTasks

I thought I'd delete my question but then figured it took me so long digging around to find my answer that maybe another question floating around SO wouldn't hurt...

(in this particular case, submitting to another service is going to take near as long as writing to the database anyway, so I'll probably forego the async processing for this api method, but at least now I know for when I actually do need to do it)

like image 154
jleach Avatar answered Sep 17 '22 22:09

jleach