-2

I have async action responding to a HTTP POST via web api 1.0. I need to do 2 things when I receive this request:

  1. Do a database insert and return the identity of that new entry to the WebApp that called the function.
  2. Using that identity to do a whole bunch work that is I/O heavy, that they WebApp and the user don't immediately care about.

In a perfect world I would put data on a queue somewhere and have a little worker to handle the queue. Since I can't immediately do that, what is the best way to make sure this work gets done without impacting the user.

[HttpPost]
public async Task<int> Post([FromBody]Object myObject)
{
    return await new ObjectLogic().InsertObject(myObject);
}
public async Task<int> InsertObject(Object myObject)
{
    var id = await new ObjectData().InsertObjectRoot(myObject);
    Task.Run(() =>  new ObjectData().ObjectWork(id, myObject));
    return id;
}

This is the solution I came up but I think there has to be something better since I am bascially stealing of thread from the thread pool until my work is finished. Is there a better way? I think I could use ConfigureAwait(false) in my InsertObject method since I really dont' care about the context there.

// await async function but use ConfigureAwait
public async Task<int> InsertObject(Object myObject)
{
    var id = await new ObjectData().InsertObjectRoot(myObject);
    await new ObjectData().ObjectWork(id, myObject).ConfigureAwait(false);
    return id;
}
Calidus
  • 1,374
  • 2
  • 14
  • 31
  • Are you concerned that you will run out of threads? Thats possible... Why can't you implement your queue idea on a separate thread? – Grantly Jan 03 '18 at 19:54
  • One easy rule to follow is that Task.Run is always a bad idea for IO bound work. – Crowcoder Jan 03 '18 at 19:55
  • you should do some analysis there. If you hit IO bottle neck then don't worry about async. If you hit thread pool bottle neck then do async. – Steve Jan 03 '18 at 19:57
  • Actually, you are not stealing any threads. https://stackoverflow.com/questions/37419572/if-async-await-doesnt-create-any-additional-threads-then-how-does-it-make-appl – Vlad Jan 03 '18 at 20:00
  • @Vlad Task.Run is taking a thread. – Crowcoder Jan 03 '18 at 20:01
  • @Crowcoder await gives up a thread. so its not a big deal – Steve Jan 03 '18 at 20:02
  • 1
    There are plenty of posts on how to implement bad idea of fire-and-forget. Make sure to search... – Alexei Levenkov Jan 03 '18 at 20:07
  • @Steve what is "gives up a thread"? Await does not tie up a thread at all for IO work but Task.Run will hog a thread even if awaited. – Crowcoder Jan 03 '18 at 20:09
  • await will let the current thread do other tasks while waiting. so its giving up the right on current thread. when await returns the new spawned thread would have finished so the thread count is the same as synchronized calls – Steve Jan 03 '18 at 20:11
  • @Grantly not really just trying to learn. – Calidus Jan 03 '18 at 20:16
  • @AlexeiLevenkov thanks I could not think of the phrasing fire and forget. that is what I needed. – Calidus Jan 03 '18 at 20:18
  • Don't understand why there are people down voting this question. It is a legitimate question and generated a really good answer. Sure there are false statements, but what is the point of asking questions if every statement in it has to be correct? Is C# community too negative? – Xiaoguo Ge Jan 03 '18 at 22:59

1 Answers1

2

One question is whether your Web API should do anything other than

  • receive the request
  • place it on a queue
  • response with an id to indicate that the request has been received.

It's going to depend to some degree on what sort of load you're expecting or might possibly see. But if you're concerned about the number of available threads from the outset then perhaps the answer is that your Web API does nothing but the above steps.

The queue could be a literal queue, like MSMQ (or whatever is popular now.) Or it could consist of a record inserted into a table. A separate Windows service could then process that queue and do the I/O heavy work. It doesn't even have to be on the same server. You can scale it separately.

If the user does want some eventual indication then they could poll for it at intervals using the id that you returned. But for me the key is in this statement:

Using that identity to do a whole bunch work that is I/O heavy, that the WebApp and the user don't immediately care about.

The job of a web application is to serve responses - IOW, to do what the user does care about. If it's long-running, I/O heavy work that the user doesn't care about then I'd consider offloading it.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • First, thanks for the response. I believe you are correct using MSMQ or something similar is the long term solution. I am looking for a baby step in the right direct since I currently have the existing code running in production and it works. It seems that 'HostingEnvironment.QueueBackgroundWorkItem' might be that baby step since it registers the task. – Calidus Jan 03 '18 at 20:39