Task.Run vs. QueueUserWorkItem

Performance freak
Search for a command to run...

Performance freak
No comments yet. Be the first to comment.
When you use Response.Redirect with its second parameter set to true, it triggers a call to Response.End() Response.Redirect("Login.aspx"); // default is true Response.Redirect("Login.aspx", true); // same as above This causes AS...

Introduction In SQL Server, the bit data type is used to store boolean values (0 for false, 1 for true). However, when working with SQL Server in a .NET application using Entity Framework or LINQ to SQL, the bit field is automatically converted to a ...

System.Threading.ThreadAbortException: Thread was being aborted. at System.Threading.Thread.AbortInternal() To avoid the ThreadAbortException error when using Response.Redirect, setting the second parameter to false is generally sufficient. However,...

In Visual Studio; Solution Explorer-Project-Properties-Application. Check "Auto-Generate binding redirects" When this is selected, versions are specified in the web.config file after the Nuget package updates. When is binding necessary? If there is...

ThreadPool.QueueUserWorkItem:Helps bypass limitations when trying to run async code inside IHttpModule.
Does not interfere with the ASP.NET pipeline.
Email sending does not block the main thread.
Calling SendMailAsync().Wait() is safe here because the operation runs on a separate thread.
The .NET ThreadPool manages a set of worker threads in the background, always on standby.
QueueUserWorkItem submits a task to this pool—if a thread is available, it runs immediately; if not, it’s queued.
That means it does not create a new thread per call, making it lightweight and efficient.
| Feature | QueueUserWorkItem | Task.Run |
| Nature | Fire-and-forget | Suitable for async/await chains |
| ASP.NET Web Forms | 100% safe and compatible | Compatible, but caution required |
Sample usage of ThreadPool.QueueUserWorkItem:
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
PublicFunctions.SendMailAsync(body, to, subject).Wait();
}
catch (Exception ex)
{
// fallback log
}
});
A task started via Task.Run(...) may not complete before the HTTP response is sent.
If the task takes too long or exceptions pile up, IIS’s thread pool can get overwhelmed.
Task.Run(async () =>
{
try
{
await PublicFunctions.SendMailAsync(...);
}
catch (Exception ex)
{
// fallback log
}
});
| Scenario | Recommendation |
| Very short and simple tasks | ✅ QueueUserWorkItem |
| Targeting .NET 4.0 or earlier | ✅ QueueUserWorkItem |
| Involving async/await chains | ✅ Task.Run |
The operation is short-lived,
No I/O-heavy logic or file/database writing involved,
It's a fire-and-forget scenario,
Then the simplest, most reliable and performance-friendly choice is:
ThreadPool.QueueUserWorkItem + .Wait()
QueueUserWorkItem a smart choice?Lightweight: No task scheduling overhead, it directly enqueues to the ThreadPool.
Legacy-friendly: 100% compatible with Web Forms and older ASP.NET infrastructure.
Ideal for fire-and-forget: You don’t need the result; you just want to trigger the action.
Task.Run make more sense?If the task involves multiple awaits (e.g., send email + log + call API),
If you need to act based on the result of the task,
If you're simply sending an error notification email, there's no need to use Task.Run.
The most sensible, lightweight, and battle-tested solution:
ThreadPool.QueueUserWorkItemwith.Wait()
Lower resource usage
More predictable behavior
Works perfectly in global error handlers or other rare but critical paths