Josh
Josh

Reputation: 350

Should I use Task.Run() for BackgroundService that does I/O

I have an ASP.NET Core 5 API that needs to do some background tasks. These background tasks will run for the entirety of the applications life, and will be reading azure queues so spending a lot of time sitting doing nothing.

I have read that for I/O you shouldn't use Task.Run() as it's inefficient, but if in my case I'm not waiting on an I/O call, I'm waiting on a background task that happens to do a lot of I/O calls, is that ok? It seems if I don't use Task.Run() then every time my background task gets something from the queue and so the code after await continues, then it will be blocking some main thread in ASP that spooled up the task in the first place.

Upvotes: 0

Views: 3547

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 457302

These background tasks will run for the entirety of the applications life, and will be reading azure queues so spending a lot of time sitting doing nothing.

I agree with others' comments: by far the best solution is to move these out of the ASP.NET process completely. Azure Functions, for example, has built-in support for Azure Storage Queues. Benefits of Azure Functions over ASP.NET in-process include:

  • Your API (ASP.NET) can scale independently from your backend (Azure Function).
  • Scaling on the backend is automatic. Up as high as you want, potentially down to zero.
  • The backend process has its own thread pool, so there's no interference with the ASP.NET thread pool.

I have read that for I/O you shouldn't use Task.Run() as it's inefficient

Task.Run should be avoided in ASP.NET's request processing pipeline. It's always inefficient regardless of the type of work being done.

Task.Run can be useful for background tasks in an ASP.NET process, in particular if there's blocking work done during startup of the background task.

The main thing with Task.Run on ASP.NET is that you don't want to interfere with the ASP.NET thread pool heuristics. Regularly queueing work to the thread pool is a problem, since you're stealing threads and then re-injecting them on a regular basis. So using Task.Run when processing each HTTP request or when processing each queue item would be a bad idea. A single Task.Run done once at startup doesn't matter.

It seems if I don't use Task.Run() then every time my background task gets something from the queue and so the code after await continues, then it will be blocking some main thread in ASP that spooled up the task in the first place.

This is a valid concern if there's blocking work being done during your message processing.

One way to think about async/await is that each method is broken up into pieces (at each await point), and each of those pieces is scheduled separately, one at a time. Each time a piece is scheduled, it takes a thread pool thread, runs the piece, and then returns the thread. As long as the work is fast, this is fine, but if one of those pieces has blocking work, then the ASP.NET thread pool heuristics can be impacted.

Upvotes: 3

GlennSills
GlennSills

Reputation: 4187

Checkout Background tasks with hosted services in ASP.NET Core to see how do do this in a separate process. That may be your best bet. You also use background processes within a web application if you want. Within the background task, use the synchronous API to do your I/O and use await.

Upvotes: 1

Related Questions