129

I have a method in ASP.NET application, that consumes quite a lot of time to complete. A call to this method might occur up to 3 times during one user request, depending on the cache state and parameters that user provides. Each call takes about 1-2 seconds to complete. The method itself is synchronous call to the service and there is no possibility to override the implementation.
So the synchronous call to the service looks something like the following:

public OutputModel Calculate(InputModel input)
{
    // do some stuff
    return Service.LongRunningCall(input);
}

And the usage of the method is (note, that call of method may happen more than once):

private void MakeRequest()
{
    // a lot of other stuff: preparing requests, sending/processing other requests, etc.
    var myOutput = Calculate(myInput);
    // stuff again
}

I tried to change the implementation from my side to provide simultaneous work of this method, and here is what I came to so far.

public async Task<OutputModel> CalculateAsync(InputModel input)
{
    return await Task.Run(() =>
    {
        return Calculate(input);
    });
}

Usage (part of "do other stuff" code runs simultaneously with the call to service):

private async Task MakeRequest()
{
    // do some stuff
    var task = CalculateAsync(myInput);
    // do other stuff
    var myOutput = await task;
    // some more stuff
}

My question: Do I use the right approach to speed up the execution in ASP.NET application or am I doing unnecessary job trying to run synchronous code asynchronously?

Can anyone explain why the second approach is not an option in ASP.NET (if it is really not)?

Also, if such approach is applicable, do I need to call such method asynchronously if it is the only call we might perform at the moment (I have such case, when no other stuff there is to do while waiting for completion)?

Most of the articles in the net on this topic covers using async-await approach with the code, that already provides awaitable methods, but that's not my case. Here is the nice article describing my case, which doesn't describe the situation of parallel calls, declining the option to wrap sync call, but in my opinion my situation is exactly the occasion to do it.

starball
  • 20,030
  • 7
  • 43
  • 238
Eadel
  • 3,797
  • 6
  • 38
  • 43

3 Answers3

165

It's important to make a distinction between two different types of concurrency. Asynchronous concurrency is when you have multiple asynchronous operations in flight (and since each operation is asynchronous, none of them are actually using a thread). Parallel concurrency is when you have multiple threads each doing a separate operation.

The first thing to do is re-evaluate this assumption:

The method itself is synchronous call to the service and there is no possibility to override the implementation.

If your "service" is a web service or anything else that is I/O-bound, then the best solution is to write an asynchronous API for it.

I'll proceed with the assumption that your "service" is a CPU-bound operation that must execute on the same machine as the web server.

If that's the case, then the next thing to evaluate is another assumption:

I need the request to execute faster.

Are you absolutely sure that's what you need to do? Are there any front-end changes you can make instead - e.g., start the request and allow the user to do other work while it's processing?

I'll proceed with the assumption that yes, you really do need to make the individual request execute faster.

In this case, you'll need to execute parallel code on your web server. This is most definitely not recommended in general because the parallel code will be using threads that ASP.NET may need to handle other requests, and by removing/adding threads it will throw the ASP.NET threadpool heuristics off. So, this decision does have an impact on your entire server.

When you use parallel code on ASP.NET, you are making the decision to really limit the scalability of your web app. You also may see a fair amount of thread churn, especially if your requests are bursty at all. I recommend only using parallel code on ASP.NET if you know that the number of simultaneous users will be quite low (i.e., not a public server).

So, if you get this far, and you're sure you want to do parallel processing on ASP.NET, then you have a couple of options.

One of the easier methods is to use Task.Run, very similar to your existing code. However, I do not recommend implementing a CalculateAsync method since that implies the processing is asynchronous (which it is not). Instead, use Task.Run at the point of the call:

private async Task MakeRequest()
{
  // do some stuff
  var task = Task.Run(() => Calculate(myInput));
  // do other stuff
  var myOutput = await task;
  // some more stuff
}

Alternatively, if it works well with your code, you can use the Parallel type, i.e., Parallel.For, Parallel.ForEach, or Parallel.Invoke. The advantage to the Parallel code is that the request thread is used as one of the parallel threads, and then resumes executing in the thread context (there's less context switching than the async example):

private void MakeRequest()
{
  Parallel.Invoke(() => Calculate(myInput1),
      () => Calculate(myInput2),
      () => Calculate(myInput3));
}

I do not recommend using Parallel LINQ (PLINQ) on ASP.NET at all.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    Thanks a lot for a detailed answer. One more thing I want to clarify. When I start execution using `Task.Run`, the content is ran in another thread, that is taken from the thread pool. Then there is no need at all to wrap blocking calls (such as mine call to service) into `Run`s, because they will always consume one thread each, that will be blocked during execution of the method. In such situation the only benefit that is left from `async-await` in my case is performing several actions at a time. Please, correct me if I'm wrong. – Eadel Jan 28 '14 at 15:07
  • 1
    Also, just to specify, the "service" is a call to remote web-service, that is not CPU-bound to our server, but it needs some time to get the response from the remote machine. On the topic of the second assumption, we provide user the option to do some work during most of the requests, but in our case both speed and server load is important. The application is not an internal one, so there is a possibility of big amount of requests withing peak load. – Eadel Jan 28 '14 at 15:12
  • 2
    Yes, the only benefit you're getting from `Run` (or `Parallel`) is concurrency. The operations are still each blocking a thread. Since you say the service is a web service, then I do not recommend using `Run` or `Parallel`; instead, write an asynchronous API for the service. – Stephen Cleary Jan 28 '14 at 15:34
  • 3
    @StephenCleary. what do you mean by "...and since each operation is asynchronous, none of them are actually using a thread..." thanks! – alltej Sep 19 '16 at 14:27
  • 4
    @alltej: For truly asynchronous code, [there is no thread](http://blog.stephencleary.com/2013/11/there-is-no-thread.html). – Stephen Cleary Sep 19 '16 at 14:35
  • 1
    @StephenCleary:I have a library that does everything via `HttpWebRequest.GetResponse`. I have a situation where I do need to call it from ASP.NET and I want N calls in parallel, to be more responsive. But I think you're saying this will use threads that will hurt performance in other ways. When you say "then the best solution is to write an asynchronous API for it.", do you mean that this would have to be completely rewritten to use `BeginGetResponse` instead? If that is not feasible, is there any other way to accomplish the parallel execution that I want? – Joshua Frank Nov 13 '18 at 22:24
  • 5
    @JoshuaFrank: Not directly. Code behind a synchronous API must block the calling thread, by definition. You *can* use an asynchronous API like `BeginGetResponse` and start several of those, and then (synchronously) block until all of them are complete, but this kind of approach is tricky - see https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html and https://msdn.microsoft.com/en-us/magazine/mt238404.aspx . It's usually easier and cleaner to adopt `async` all the way, if possible. – Stephen Cleary Nov 15 '18 at 12:52
  • "If your "service" is a web service or anything else that is I/O-bound, then the best solution is to write an asynchronous API for it." - do you have an example? – Daniel Dušek Apr 03 '21 at 15:20
  • @DanielDušek: I/O-bound services include calls to Web APIs and databases. – Stephen Cleary Apr 03 '21 at 23:59
1

I found that the following code can convert a Task to always run asynchronously

private static async Task<T> ForceAsync<T>(Func<Task<T>> func)
{
    await Task.Yield();
    return await func();
}

and I have used it in the following manner

await ForceAsync(() => AsyncTaskWithNoAwaits())

This will execute any Task asynchronously so you can combine them in WhenAll, WhenAny scenarios and other uses.

You could also simply add the Task.Yield() as the first line of your called code.

Jay Byford-Rew
  • 5,736
  • 1
  • 35
  • 36
1

this is probably the easiest generic way in your case

return await new Task(
    new Action(
        delegate () { 
          // put your synchronous code here
        }
    )
);
david.barkhuizen
  • 5,239
  • 4
  • 36
  • 38