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

.NET 6中如何从Hosted Service启动长期运行的Scoped Service

解决方案:在.NET 6中让Manager HostedService与Scoped Client同时运行

你的问题核心是直接await了Client的DoWork方法,而DoWork是一个无限循环任务,导致Manager的执行流程被阻塞在这一步,无法进入后续循环。要实现两者同时运行,需要将Client的任务作为后台任务启动,而非同步等待其完成,同时要正确管理Scoped Service的生命周期。

修改后的Manager代码示例

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    // 存储所有启动的Client任务,用于后续管理与优雅关闭
    var clientTasks = new List<Task>();

    // 示例:启动一个Client(实际可根据业务逻辑在循环中按需启停)
    var scope = Services.CreateScope();
    var client = scope.ServiceProvider.GetRequiredService<IClient>();
    
    // 启动Client的后台任务,不阻塞Manager流程
    var clientTask = Task.Run(async () =>
    {
        try
        {
            await client.DoWork(stoppingToken);
        }
        finally
        {
            // Client任务结束后释放Scope,避免资源泄漏
            scope.Dispose();
        }
    }, stoppingToken);
    clientTasks.Add(clientTask);

    // Manager自身的循环逻辑
    while (!stoppingToken.IsCancellationRequested)
    {
        await Task.Delay(1000, stoppingToken);

        // 这里可添加业务逻辑:比如按需启动新Client
        // if (需要启动新Client的条件)
        // {
        //     var newScope = Services.CreateScope();
        //     var newClient = newScope.ServiceProvider.GetRequiredService<IClient>();
        //     var newClientTask = Task.Run(async () =>
        //     {
        //         try { await newClient.DoWork(stoppingToken); }
        //         finally { newScope.Dispose(); }
        //     }, stoppingToken);
        //     clientTasks.Add(newClientTask);
        // }

        // 清理已完成的任务,避免列表无限膨胀
        clientTasks.RemoveAll(t => t.IsCompleted);
    }

    // 应用停止时,等待所有Client任务完成,保证优雅关闭
    await Task.WhenAll(clientTasks);
}

关键说明

  1. 非阻塞启动Client任务:用Task.Run将Client的DoWork包装为后台任务,Manager无需等待其完成即可继续执行自身的循环逻辑。
  2. 正确管理Scoped生命周期:将Scope的释放放在Client任务的finally块中,确保Client运行期间Scope始终有效,避免依赖注入的服务被提前回收。
  3. 任务管理与优雅关闭:通过clientTasks列表跟踪所有启动的Client任务,在应用停止时调用Task.WhenAll等待所有Client任务结束,防止强制终止导致的资源泄漏。
  4. 支持单独控制Client生命周期:如果需要单独停止某个Client(而非依赖全局的停止令牌),可以为每个Client创建独立的CancellationTokenSource,示例如下:
// 为单个Client创建独立的取消令牌(关联全局令牌,保证应用停止时也能触发取消)
var clientCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
var clientTask = Task.Run(async () =>
{
    try
    {
        await client.DoWork(clientCts.Token);
    }
    finally
    {
        scope.Dispose();
        clientCts.Dispose();
    }
}, stoppingToken);

// 后续需要停止该Client时,调用:
// clientCts.Cancel();

这样就能实现Manager与多个Client同时运行,且Client的生命周期可灵活控制,无需阻塞Manager的执行流程。

内容的提问来源于stack exchange,提问作者Brad R.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 20:55:24