Introduction
By using the fire-and-forget approach appropriately, you can improve the responsiveness and performance of your .NET Core applications while ensuring that important background tasks are performed efficiently.
In this article, we will focus on Task.Run and will learn how we can perform background work with Task.Run.
What is a Task.Run?
Task.Run queues the specified work to run on the thread pool and returns a Task object representing that work. This allows the work to be performed asynchronously without blocking the main thread.
How Task.Run Works: Control Flow Diagram
- Task Creation: The Task.Run method creates a new Task object.
- Queueing Work: The task is queued to the thread pool.
- Thread Pool Execution: A thread pool thread picks up the task and executes it.
- Task Completion: The task is completed, and the Task object is marked as completed, faulted, or canceled, depending on the outcome.
Main Thread Thread Pool
| |
| |
| Task.Run |
|---------> Task Created |
| |
| |
| |
| | Task Queued
| |-----------------> Task Executed
| | |
| | |
| Task Continuation | | Task Completed
|<----------------------------|<---------------------|
| |
Task.Run in .NET Core allows you to offload work to a background thread, which can help improve the responsiveness of your application by freeing up the main thread for other tasks.
Below is few Real time use cases where we can use Task.Run.
Logging
This is a perfect use case, and each and every application is doing logging. Let’s understand how we can improve performance.
Suppose after a successful database operation, we want to log operation results, and if we do so sequentially, then UI will remain blocked. Of course, this is not important work from the UI user or client, so if we perform Logging in the background, then that will increase response time.
Detailed implementation
Logger Service: we are logging in using Task.Run in the code below.
public interface ILoggingService
{
void LogInformation(string message);
}
public class LoggingService : ILoggingService
{
public void LogInformation(string message)
{
Task.Run(() =>
{
// Simulate logging operation
System.IO.File.AppendAllText("log.txt", $"{DateTime.UtcNow}: {message}{Environment.NewLine}");
});
}
}
Controller
[ApiController]
[Route("[controller]")]
public class ExampleController : ControllerBase
{
private readonly ILoggingService _loggingService;
public ExampleController(ILoggingService loggingService)
{
_loggingService = loggingService;
}
[HttpGet]
public IActionResult Get()
{
// Reading Work to DB
///
_loggingService.LogInformation("Get method called.");
return Ok("Hello, world!");
}
}
2. Generating Reports
Suppose the User wants to submit a request to generate a report to fetch from the Database. Then, instead of keeping users blocked, we can submit a report generation request, and then all processing will happen in the background.
public void GenerateReport(int reportId)
{
Task.Run(() =>
{
ReportService.Generate(reportId);
});
}
3. Uploading files
Suppose the User wants to upload files from the UI side, and we want that processing to keep happening in the background, and the user will be free asap for other operations.
public void UploadFile(string filePath)
{
Task.Run(() =>
{
FileService.Upload(filePath);
});
}
4. Cache Clean
Clear cache asynchronously to free up memory or refresh data without blocking the main thread.
public void ClearCache()
{
Task.Run(() =>
{
CacheService.Clear();
});
}
5. Database Cleanup
After Successfully placing an order, we need to delete records from multiple inventory tables. We can do that operation in the background so that the user will get a response asap and can start another activity.
public void CleanupDatabase()
{
Task.Run(() =>
{
DatabaseService.CleanupOldRecords();
});
}
6. Refreshing API Tokens
Refresh API tokens in the background to ensure they are up-to-date without interrupting ongoing operations.
public void RefreshApiToken()
{
Task.Run(() =>
{
TokenService.Refresh();
});
}
7. Sending Emails
- This is perfect use case, suppose user order one item and after successful placed we need to send email to end user at background so that user will get response asap.
- Another user case is after user registration. We can send an email in the background so that UI can get a response asap.
public class RegistrationService
{
private readonly EmailService _emailService;
public RegistrationService(EmailService emailService)
{
_emailService = emailService;
}
public void RegisterUser(User user)
{
// logic for registration or DB call
// Send confirmation email in background
Task.Run(() =>
{
_emailService.Send(user.Email, "Welcome!", "Thank you for registering.");
});
}
}
Conclusion
Using Task.Run effectively can greatly enhance the performance and responsiveness of .NET Core applications by offloading CPU-bound tasks to background threads and allowing the main thread to handle other operations.