You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# API异步执行后台任务时前端卡顿问题排查

问题分析与解决方案

核心问题出在你用了async void的异步写法——这种写法仅适用于UI事件处理场景,在ASP.NET环境中,调用async void方法后,ASP.NET会把这个异步操作绑定到当前请求的上下文,哪怕你的控制器和DoSomething方法已经执行完毕返回,ASP.NET仍会等待这个后台通知任务完成后才会真正结束请求,这就是为什么前端会一直卡顿等待响应。

快速修复方案

1. 替换async void为async Task

async void是非标准的异步写法,服务端场景必须改用async Task:

public class Subscriber
{
    // 原有同步方法
    public void NotifySubscriber(string message)
    {
        // 发送通知的耗时逻辑
    }

    // 替换async void为async Task
    public async Task NotifySubscriberAsync(string message)
    {
        // 如果NotifySubscriber是阻塞IO或CPU密集型操作,用Task.Run包装;如果是IO密集,建议直接改为原生异步IO方法
        await Task.Run(() => NotifySubscriber(message)).ConfigureAwait(false);
    }
}

2. 正确实现“火即忘”调用

在DoSomething中调用异步方法时,不使用await,同时脱离请求上下文并处理异常(避免任务抛异常导致进程崩溃):

public class MyClassService
{
    public void DoSomething(string input)
    {
        // 服务自身业务逻辑

        Subscriber s = new Subscriber();
        // 调用异步方法,不等待完成,但捕获并处理异常
        _ = Task.Run(async () =>
        {
            try
            {
                await s.NotifySubscriberAsync("my message").ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                // 这里添加异常日志记录,比如写入日志系统
                // Logger.LogError(ex, "订阅通知发送失败");
            }
        }).ConfigureAwait(false);
    }
}

推荐的可靠方案:后台任务队列

如果通知任务需要高频执行或保证可靠性(避免应用重启时丢失未完成任务),建议使用ASP.NET的后台任务队列:

1. 定义任务队列接口与实现

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new();
    private readonly SemaphoreSlim _signal = new(0);

    public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
    {
        if (workItem == null) throw new ArgumentNullException(nameof(workItem));
        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);
        return workItem;
    }
}

2. 实现后台服务处理队列

public class QueuedHostedService : BackgroundService
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly ILogger<QueuedHostedService> _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILogger<QueuedHostedService> logger)
    {
        _taskQueue = taskQueue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = await _taskQueue.DequeueAsync(stoppingToken);
            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "执行后台订阅通知任务时出错");
            }
        }
    }
}

3. 注册服务到依赖注入容器

在Program.cs中添加:

builder.Services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
builder.Services.AddHostedService<QueuedHostedService>();

4. 在业务服务中使用队列

public class MyClassService
{
    private readonly IBackgroundTaskQueue _taskQueue;

    // 通过构造函数注入队列
    public MyClassService(IBackgroundTaskQueue taskQueue)
    {
        _taskQueue = taskQueue;
    }

    public void DoSomething(string input)
    {
        // 服务自身业务逻辑

        // 将通知任务加入后台队列,完全脱离当前请求上下文
        _taskQueue.QueueBackgroundWorkItem(async token =>
        {
            Subscriber s = new Subscriber();
            s.NotifySubscriber("my message");
            // 如果已改为异步方法,使用await:
            // await s.NotifySubscriberAsync("my message");
        });
    }
}

内容的提问来源于stack exchange,提问作者user978426

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:53:15