6

I have read these articles:

ASP.NET Core Performance Best Practices

When should I use Task.Run in Asp.Net Core?

Is it allowed to use Task.Run in an ASP.NET Core controller?

In the first article in docs.microsoft we can see this statement:

Call data access, I/O, and long-running operations APIs asynchronously if an asynchronous API is available. Do not use Task.Run to make a synchronous API asynchronous.

I don't understand this statement well. I'm confused about where and when I should use Task.Run and where and when I should (can) write async web API. For example, consider this method:

public int DoSomeHeavyWork()
{
   // Consider heavy computations
   int i = 10;
   i = i * 2;
   i = i + 4;
   return i;
}

and this method:

public void SaveSomthingInDb(Order ord)
{
    _dbContext.Orders.Add(ord);
    _dbContext.SaveChange();
}

according to the above statement, I should write a web method synchronously because there is no async version:

public IActionResult API_DoSomeHeavyWork()
{
    return Ok(DoSomeHeavyWork());
}

but for method 2 I can change it this way:

public async Task SaveSomthingInDbAsync(Order ord)
{
    _dbContext.Orders.Add(ord);
    await _dbContext.SaveChangeAsync();
}

so I can write an API for that like this:

public async Task<IActionResult> API_SaveSomthingInDbAsync(Order ord)
{
    await SaveSomthingInDbAsync(ord);
    return Ok("Object Added");
}

I don't know هs my perception of this issue is correct or not? Are write these methods true? Is there any way to run the first API async?

Thanks


Edit 1)

Thanks to "Stephen Cleary".

If we assume this sentence: We use async method in web methods of Web API to prevent blocking thread pool threads and take more requests...

Please consider this code:

await _context.Order.ToListAsync();

according to this code, there is an assembly named Microsoft.EntityFrameworkCore and it has a async method named ToListAsync:

public static Task<List<TSource>> ToListAsync<TSource>(this IQueryable<TSource> source, CancellationToken cancellationToken = default(CancellationToken))

First question is how ToListAsync implemented? Is it implemented like this?

public static Task<List<TSource>> ToListAsync<TSource>(...)
{
    Task.Run(()=>{ <Some thing>.ToList() });
}

Second question is if the method DoSomeHeavyWork placed in a separate assembly and rewrite it this way:

public Task<int> DoSomeHeavyWork()
{
   Task.Run(() => 
   {
       // Consider heavy computations
       int i = 10;
       i = i * 2;
       i = i + 4;
       return i;
   }
}

Can I then call it async from my web API and free thread pool thread?

Thanks

Arian
  • 12,793
  • 66
  • 176
  • 300
  • 5
    "How to make CPU intensive operation not to consume CPU (at least in a visible/measurable way)" type of questions are always entertaining... What exactly you hope to achieve by shifting CPU intensive operation from one thread to another when you likely can't control incoming flow of such requests? – Alexei Levenkov Feb 02 '22 at 21:30
  • I understand that a CPU intensive operation must use CPU. But my question is about web API and thread pool. If we can free thread pool in some way, so why we don't do that? For No.1 heavy opeation, thread from thread pool will bock until entire operation complete. – Arian Feb 03 '22 at 04:30

2 Answers2

9

I'm confused about where and when I should use Task.Run

On ASP.NET, almost never. You can adopt "never" as a general rule.

and where and when I should (can) write async web API.

Your controller actions should be asynchronous if and only if they invoke asynchronous code. Generally speaking, you should prefer asynchronous APIs at the lowest level for all code that performs I/O. Then your action methods are asynchronous only if they need to call that asynchronous code.

I should write a web method synchronously because there is no async version ... but for method 2 I can change it this way

Yup.

Is there any way to run the first API async?

Think of it this way: if there is synchronous (CPU-bound) work to do, then that work needs a thread. It's already running on a thread pool thread (because web requests run on threads from thread pool). Task.Run will move that work to a different thread pool thread, but it will still run on a thread pool thread. So Task.Run doesn't help anything; it just adds overhead and provides no benefit. Hence the general rule of "don't use Task.Run on ASP.NET".

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I'm pretty sure you could have just provided last paragraph for this specific question as OP seem to be confused where regular requests run. (+9000 for extra info) – Alexei Levenkov Feb 03 '22 at 04:43
  • @StephenCleary: Thanks dear friend. Could you please see my **Edit 1**. I want to clear these questions and help other people like me that have my questions – Arian Feb 03 '22 at 06:19
  • 1
    @Arian: No, and no. Truly asynchronous code [uses no thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html). – Stephen Cleary Feb 03 '22 at 06:42
  • @StephenCleary: Can you please show me some links an clues that guide how to convert a synchronous method to async version? Thanks – Arian Feb 04 '22 at 14:33
  • I have another question. Is it possible to write every sync method as async **truly**? – Arian Feb 04 '22 at 17:52
  • 1
    @Arian: I recommend starting at the lowest level and using asynchronous APIs there, and then letting `async` grow through the codebase. It's not possible to write every sync method as truly async; many methods are just naturally synchronous. – Stephen Cleary Feb 04 '22 at 18:30
1

Do not use Task.Run to make a synchronous API asynchronous.

Using Task.Run in ASP.Net (FX and Core) is an anti-pattern.

The expectation when you see/use Task.Run is that you are offloading work that will be completed in the background. Often in ASP.Net you might think you want to do this so that a HttpRequest can be completed and a response sent back to the caller without waiting for the logic to be actually complete, the idea being that you can make the front-end responsive by responding as early as we can.

To many, this sounds like a legitimate asynchronous processing solution when using APIs, especially given that this works really well in desktop applications and even MVC, but in Web API many threads are aborted when the associated request is completed (or aborted). This means that your background processing may also be aborted.

In ASP.Net the best practice for background processing, especially if you need to support large scale or high frequency of requests is to use a queue pattern with a different service runtime that processes the items on the queue, totally decoupled from the http request process itself.

As an example, the Azure platform encourages this pattern by allowing you to deploy web jobs into the same App Service that runs your ASP.Net implementation. This managed hosting model expects your HTTP processing to be as short as possible, all CPU heavy, asynchronous or parallel processing be submitted as messages or events in a queue. You can then code your web jobs (or functions) to be triggered to execute in sequence when items are added to the queue.

This answer by Stephen Cleary is absolutely correct, this is a supplementary post, exploring the common reasons that people even attempt to use Task.Run in this context.

The following posts explore different ways that you can and can't use background threads in ASP.Net:

One of the key issues with long running processes in ASP.Net is that the web host process may kill your running logic with very little notice, this can be very hard to track down in logs and happens a lot more frequently than you expect. Rather than fight this natural phenomenon, work with it, use the HTTP runtime to schedule work to be completed by an actual background process. A side effect of doing is that it will enable you to scale out a lot more effectively as your userbase grows.

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81