2

I want to performa an asynchronous DB Query in C# that calls a stored procedure for a Backup. Since we use Azure this takes about 2 minutes and we don't want the user to wait that long.

So the idea is to make it asynchronous, so that the task continues to run, after the request.

[HttpPost]
public ActionResult Create(Snapshot snapshot)
{
    db.Database.CommandTimeout = 7200;
    Task.Run(() => db.Database.ExecuteSqlCommandAsync("EXEC PerformSnapshot @User = '" + CurrentUser.AccountName + "', @Comment = '" + snapshot.Comment + "';"));
    this.ShowUserMessage("Your snapshot has been created.");
    return this.RedirectToActionImpl("Index", "Snapshots", new System.Web.Routing.RouteValueDictionary());
}

I'm afraid that I haven't understood the concept of asynchronous taks. The query will not be executed (or aborted?), if I don't use the wait statement. But actually "waiting" is the one thing I espacially don't want to do here.

So... why am I forced to use wait here?

Or will the method be started, but killed if the requst is finished?

0

4 Answers 4

1

We don't want the user to wait that long.

async-await won't help you with that. Odd as it may sound, the basic async-await pattern is about implementing synchronous behavior in a non-blocking fashion. It doesn't re-arrange your logical flow; in fact, it goes to great lengths to preserve it. The only thing you've changed by going async here is that you're no longer tying up a thread during that 2-minute database operation, which is a huge win your app's scalability if you have lots of concurrent users, but doesn't speed up an individual request one bit.

I think what you really want is to run the operation as a background job so you can respond to the user immediately. But be careful - there are bad ways to do that in ASP.NET (i.e. Task.Run) and there are good ways.

Sign up to request clarification or add additional context in comments.

Comments

0

Dave, you're not forced to use await here. And you're right - from user perspective it still will take 2 minutes. The only difference is that the thread which processes your request can now process other requests meanwhile database does its job. And when database finishes, the thread will continue process your request.

Say you have limited number of threads capable to process HTTP request. This async code will help you to process more requests per time period, but it won't help user to get the job done faster.

5 Comments

So you mean, there is no solution for this? An action that starts a thread that continues running even after the action is finished?
There are solutions, but first we need to understand requirements. What if after request processed, the background thread fails for some reason. How would you notify user that the request wasn't processed, snapshot wasn't created? Actually this on is common example of "Command" which should be submitted fast, but processing of command can take a while and user must be notified about results of processing.
As a simple example: one method saves command ("create snapshot") and snapshot itself in database (for example), starts background thread and returns commandId to user. This should be fast. Then we need second method which can give us status of command by given commandId, so "user" can use second method to obtain results. It still will take 2 minutes to execute command, but user's UI won't freeze, user can do something else meanwhile and so on.
There is no need to give a feedback to the user in this method at the moment (this is another issue). First I simply need to trigger the Query asynchronously.
If your requirements are so relaxed, you can simple use Task.Run (or ThreadPool.QueueUserWorkItem) to start background execution and return response to user without waiting task's result.
0

This seems to be down to a misunderstanding as to what async and await do.

async does not mean run this on a new thread, in essence it acts as a signal to the compiler to build a state machine, so a method like this:

Task<int> GetMeAnInt()
{
    return await myWebService.GetMeAnInt();
}

sort of (cannot stress this enough), gets turned into this:

Task<int> GetMeAnInt()
{
    var awaiter = myWebService.GetMeAnInt().GetAwaiter();
    awaiter.OnCompletion(() => goto done);
    return Task.InProgress;
    done:
        return awaiter.Result;
}

MSDN has way more information about this, and there's even some code out there explaining how to build your own awaiters.

async and await at their very core just enable you to write code that uses callbacks under the hood, but in a nice way that tells the compiler to do the heavy lifting for you.

If you really want to run something in the background, then you need to use Task:

Task<int> GetMeAnInt()
{
    return Task.Run(() => myWebService.GetMeAnInt());
}

OR

Task<int> GetMeAnInt()
{
    return Task.Run(async () => await myWebService.GetMeAnInt());
}

The second example uses async and await in the lambda because in this scenario GetMeAnInt on the web service also happens to return Task<int>.

To recap:

  1. async and await just instruct the compiler to do some jiggerypokery
    1. This uses labels and callbacks with goto
    2. Fun fact, this is valid IL but the C# compiler doesn't allow it for your own code, hence why the compiler can get away with the magic but you can't.
  2. async does not mean "run on a background thread"
  3. Task.Run() can be used to queue a threadpool thread to run an arbitrary function
  4. Task.Factory.Start() can be used to grab a brand new thread to run an arbitrary function
  5. await instructs the compiler that this is the point at which the result of the awaiter for the awaitable (e.g. Task) being awaited is required - this is how it knows how to structure the state machine.

5 Comments

Thank you. But using a thread doen't work either: The thread 0x2ac0 has exited with code 259 (0x103). Seems to be that the threads will be killed after the scope of the entity framework
@Dave could you edit the question to include the code you used that resulted in this exception?
I just used Task.Run(() => db.Database.ExecuteSqlCommandAsync("EXEC PerformSnapshot @User = '" + CurrentUser.AccountName + "', @Comment = '" + snapshot.Comment + "';")); instead
Do not use Task.Run in an ASP.NET application, ever.
@ToddMenier my bad, I forgot to mention that! The Task.Run was there as a demonstration of how it interacts with the async and await.
0

As I describe in my MSDN article on async ASP.NET, async is not a silver bullet; it doesn't change the HTTP protocol:

When some developers learn about async and await, they believe it’s a way for the server code to “yield” to the client (for example, the browser). However, async and await on ASP.NET only “yield” to the ASP.NET runtime; the HTTP protocol remains unchanged, and you still have only one response per request.

In your case, you're trying to use a web request to kick off a backend operation and then return to the browser. ASP.NET was not designed to execute backend operations like this; it is only a web tier framework. Having ASP.NET execute work is dangerous because ASP.NET is only aware of work coming in from its requests.

I have an overview of various solutions on my blog. Note that using a plain Task.Run, Task.Factory.StartNew, or ThreadPool.QueueUserWorkItem is extremely dangerous because ASP.NET doesn't know anything about that work. At the very least you should use HostingEnvironment.QueueBackgroundWorkItem so ASP.NET at least knows about the work. But that doesn't guarantee that the work will actually ever complete.

A proper solution is to place the work in a persistent queue and have an independent background worker process that queue. See the Asynchronous Messaging Primer (specifically, your scenario is "Decoupling workloads").

3 Comments

We don't need any result of that operation. We just want to trigger a stored procedure on DB Level. We don't care about results or errors
@Dave: Do you care if the procedure completes? DBs tend to get excited and start rolling everything back when their client connections are terminated unexpectedly.
No I don't care since it is capsuled in a transaction and we check some where else if the procedure has succeeded or not.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.