6

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.

jleach
  • 7,410
  • 3
  • 33
  • 60

2 Answers2

11

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)

Community
  • 1
  • 1
jleach
  • 7,410
  • 3
  • 33
  • 60
2

A database insert shouldn't take so long that you have to offload that processing to a background task. For starters just writing the task to a queue (or, as you suggested, handing off to a service) is going to take just as long but either approach should be sub-second.

However, if time is critical for you one way to speed up your response time is to make the database write as fast as possible using some form of in-memory cache so that the slower write to physical database storage is a queued background task. High-volume sites frequently use in-memory databases that implement this kind of behaviour (I've never needed one so can't help you choose a product) but you could also code this yourself just using a per-application instance list of objects and a background loop of some form.

This is where those articles you've linked apply and it gets complicated so a pre-built implementation is almost always the best approach - check out HangFire if you want a pre-built fire-and-forget implementation.

christutty
  • 952
  • 5
  • 12
  • Thanks for the answer. I'm aware that the db write is insignificant, but at the outset I figured "why not", without realizing the implications. I've dropped the idea for this scenario. I came across HangFire in my travels researching this and will definitely keep it in mind when an actual need arises. – jleach Mar 19 '16 at 15:29