Improving .NET Core User Registration Performance with a Background TransferJob Component

The article explains the typical user‑registration flow in .NET Core, demonstrates why sending SMS/email and logging should be decoupled from the main persistence step, and introduces the open‑source Weshare.TransferJob library that moves such side‑tasks to a background service to boost throughput while handling DI scopes correctly.

Fulu Network R&D Team
Fulu Network R&D Team
Fulu Network R&D Team
Improving .NET Core User Registration Performance with a Background TransferJob Component

Most developers are familiar with the basic user‑registration process: receive the registration data, persist it (database + Redis), send a confirmation SMS/email, and optionally write an operation log.

The author provides a simple pseudo‑code example that uses Task.Delay to simulate processing time and shows a straightforward sequential implementation.

While this flow works, the author argues that persisting the user data should be the only factor determining registration success; sending notifications and logging are optional and often time‑consuming. To improve response time, these side‑tasks should be executed asynchronously, separate from the main persistence logic.

Several refactoring options are discussed, including naive async/await usage, which can cause System.ObjectDisposedException when scoped services are accessed from a background Task.Run after the request ends. The correct approach is to create a new DI scope using IServiceScopeFactory inside the background task.

The article then evaluates four possible solutions for offloading the side‑tasks: message queues, Quartz, Hangfire, and the author’s preferred Weshare.TransferJob component. Message queues provide decoupling but may be overkill; Quartz and Hangfire require persistent job stores and can be heavy for simple scenarios.

Weshare.TransferJob is a lightweight library that leverages .NET Core’s HostedService to run background jobs. Integration steps include installing the NuGet package, registering the service with services.AddTransferJob();, injecting IBackgroundRunService, and calling its Transfer method to enqueue work.

public static IServiceCollection AddTransferJob(this IServiceCollection services)
{
    services.AddSingleton<IBackgroundRunService, BackgroundRunService>();
    services.AddHostedService<TransferJobHostedService>();
    return services;
}

The hosted service continuously executes queued jobs:

public class TransferJobHostedService : BackgroundService
{
    private IBackgroundRunService _runService;
    public TransferJobHostedService(IBackgroundRunService runService)
    {
        _runService = runService;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _runService.Execute(stoppingToken);
        }
    }
}

The core BackgroundRunService maintains a SemaphoreSlim and a ConcurrentQueue<LambdaExpression>. When a job is dequeued, a new DI scope is created, the expression is compiled, and the method is invoked, handling both synchronous and asynchronous returns.

public async Task Execute(CancellationToken cancellationToken)
{
    await _slim.WaitAsync(cancellationToken);
    if (queue.TryDequeue(out var job))
    {
        using var scope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
        var action = job.Compile();
        var isTask = action.Method.ReturnType == typeof(Task);
        var parameters = job.Parameters;
        var pars = new List<object>();
        if (parameters.Any())
        {
            var type = parameters[0].Type;
            var param = scope.ServiceProvider.GetRequiredService(type);
            pars.Add(param);
        }
        if (isTask)
            await (Task)action.DynamicInvoke(pars.ToArray());
        else
            action.DynamicInvoke(pars.ToArray());
    }
}

public void Transfer<T>(Expression<Func<T, Task>> expression)
{
    queue.Enqueue(expression);
    _slim.Release();
}

public void Transfer(Expression<Action> expression)
{
    queue.Enqueue(expression);
    _slim.Release();
}

Performance tests using JMeter show that moving SMS/email sending and logging to the background can increase throughput by three to four times. The component is best suited for tasks that are not transaction‑critical and whose success does not affect the main business flow.

Finally, the author shares the open‑source repository (https://github.com/fuluteam/WeShare.TransferJob) and encourages readers to star the project.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

netbackground jobTransferJob
Fulu Network R&D Team
Written by

Fulu Network R&D Team

Providing technical literature sharing for Fulu Holdings' tech elite, promoting its technologies through experience summaries, technology consolidation, and innovation sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.