Async.Task.Runner.Core 1.0.1

dotnet add package Async.Task.Runner.Core --version 1.0.1
                    
NuGet\Install-Package Async.Task.Runner.Core -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Async.Task.Runner.Core" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Async.Task.Runner.Core" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Async.Task.Runner.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Async.Task.Runner.Core --version 1.0.1
                    
#r "nuget: Async.Task.Runner.Core, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Async.Task.Runner.Core@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Async.Task.Runner.Core&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Async.Task.Runner.Core&version=1.0.1
                    
Install as a Cake Tool

⏱️ Async Task Runner (Generic In-Memory Background Tasks)

A lightweight, generic utility for running long-running or non-blocking tasks in-memory with parallel execution, enabling you to build high-performance APIs by offloading independent tasks without waiting for their completion upfront.


πŸš€ Use Case

This library is designed to solve a common problem in API design:

❓ "What if I have a long-running task that I don’t immediately need the result for, but I will later in the same request or another part of the workflow?"

Instead of awaiting the result right away, you can:

  1. Kick off the task early using StartTaskAsync(...) and get a unique TaskId.
  2. Let the task run in the background, independently.
  3. Later (when the result is actually needed), call GetTaskResultByTaskIdAsync(...) to wait for completion if necessary and retrieve the result.

This allows multiple operations to proceed in parallel, reducing overall response time and improving throughput for your APIs.


🧠 Key Features

  • βœ… Generic task result support (T)
  • βœ… In-memory result caching with 1-minute expiration
  • βœ… Thread-safe design with ConcurrentDictionary and IMemoryCache
  • βœ… Easily pluggable via Dependency Injection in ASP.NET Core
  • βœ… No external dependencies – 100% .NET built-in

Installation

Install the package via NuGet:

dotnet add package Async.Task.Runner.Core
using Async.Task.Runner.Core.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.ConfigureTaskRunnerServices();

πŸ’‘ Example Use Case: Geo Lookup in the Background

Let's say you have an external geo service (IGeoService) that takes a latitude and longitude and returns geographic information (like country, city, village).
This call might take a couple of seconds, and you only need the result at the end of your API logic, not right away.

public interface IGeoService
{
    Task<LocationInfo> GetLocationAsync(double latitude, double longitude);
}

public class GeoService : IGeoService
{
    public async Task<LocationInfo> GetLocationAsync(double latitude, double longitude)
    {
        //simulate long running task
        await System.Threading.Tasks.Task.Delay(1500);
        return new LocationInfo { CountryId = 100, CityId = 101, VillageId = 1011 };
    }
}

public class LocationInfo
{
    public long CountryId { get; set; }
    public long CityId { get; set; }
    public long VillageId { get; set; }
}

You can offload it like this:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController(ILogger<WeatherForecastController> logger) : ControllerBase
{
    private static readonly string[] Summaries =
    [
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    ];

    //simulate current location
    private static readonly double Latitude = (double)35.72828545564619;
    private static readonly double Longitude = (double)51.41550287298716;


    [HttpGet]
    [Route("GetWeatherForecast/normal")]
    public async Task<IEnumerable<WeatherForecast>> GetNormal([FromServices] IGeoService geoService)
    {
        await System.Threading.Tasks.Task.Delay(1000);
        var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .ToArray();


        var locationInfo = await geoService.GetLocationAsync(Latitude, Longitude);
        foreach (var weatherForecast in result)
        {
            weatherForecast.CountryId = locationInfo.CountryId;
        }

        return result;  //in 2.5 seconds
    }

    [HttpGet]
    [Route("GetWeatherForecast/with-task-runner-usage")]
    public async Task<IEnumerable<WeatherForecast>> GetWithTaskRunner([FromServices] IGeoService geoService,
        [FromServices] IAsyncTaskRunner<LocationInfo> locationFinderTaskRunner)
    {
        var taskId = await locationFinderTaskRunner.StartTaskAsync(async () =>
            await geoService.GetLocationAsync(Latitude, Longitude));


        await System.Threading.Tasks.Task.Delay(1000);
        var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
            .ToArray();


        var locationInfo = await locationFinderTaskRunner.GetTaskResultByTaskIdAsync(taskId);
        foreach (var weatherForecast in result)
        {
            weatherForecast.CountryId = locationInfo.CountryId;
        }

        return result; //in 1.5 seconds
    }
}
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 159 4/20/2025
1.0.0 147 4/20/2025

Initial release.