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

.NET 6新极简托管模型下自托管Web API集成问题咨询

.NET 6 BackgroundService 托管 Web API 实现方案

问题背景

现有基于.NET 6开发的后台服务,核心逻辑为继承BackgroundService的Worker类,初始实现代码如下:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IService _service;

    public Worker(ILogger<Worker> logger, IService service)
    {
        _logger = logger;
        _service = service;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _service.StartAsync().ConfigureAwait(false);
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken).ConfigureAwait(false);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        await _service.StopAsync().ConfigureAwait(false);
        await base.StopAsync(stoppingToken).ConfigureAwait(false);
    }
}

需求为在该服务内托管带控制器的.NET Core Web API,最初尝试在ExecuteAsync方法中直接添加WebApplication启动代码,但始终无法正常运行,尝试的启动代码如下:

var builder = WebApplication.CreateBuilder();
builder.RegisterWeb();
_app = builder.Build();
_app.ConfigureWebApplication(builder.Environment);
_app.Run();

使用的两个自定义扩展方法为常规Web API配置实现,代码如下:

public static void ConfigureWebApplication(this WebApplication app, IWebHostEnvironment environment)
{
    if (environment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "E.WebApi v1"));
    }           
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}

public static void RegisterWeb(this WebApplicationBuilder builder)
{
    builder.Services.AddControllers();
    builder.Services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "E.WebApi", Version = "v1" });
    });
    builder.WebHost.UseUrls("http://localhost:7153/");
}

问题根因

直接在ExecuteAsync中调用同步方法_app.Run()会阻塞当前异步执行流,导致后续后台循环逻辑完全无法执行;同时手动构建的WebApplication生命周期没有和后台服务的停止令牌绑定,服务终止时无法触发Web主机的优雅停机、资源释放逻辑,两个独立构建的主机生命周期割裂是运行异常的核心原因。不需要引入Katana,.NET 6 原生就支持在Worker Service中托管Web API,以下是两种可直接落地的方案。

实现方案

方案1:启动阶段集成Web主机(官方推荐)

不需要在Worker类内部手动构建WebApplication,直接在Program.cs启动阶段将Web API和后台服务统一注册到通用主机,由.NET主机统一管理两者的生命周期,实现最简单、稳定性最高。

  • 首先补全框架引用:如果项目是从Worker Service模板创建的,默认未携带ASP.NET Core框架,需要修改csproj文件,添加如下节点:
<ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
  • 修改Program.cs启动代码,使用WebApplication.CreateBuilder构建主机,同时注册后台服务和Web API相关功能:
var builder = WebApplication.CreateBuilder(args);

// 注册原有后台Worker服务
builder.Services.AddHostedService<Worker>();
// 注册原有业务服务
builder.Services.AddSingleton<IService, YourServiceImplementation>();

// 复用已写好的扩展方法注册Web API服务
builder.RegisterWeb();

var app = builder.Build();
// 复用已写好的扩展方法配置Web中间件管道
app.ConfigureWebApplication(app.Environment);

// 启动主机,会并行运行Web API和后台Worker任务
app.Run();

该方案下不需要修改原有Worker类的任何业务逻辑,主机启动时会自动执行ExecuteAsync中的后台循环,收到停止信号时会同时触发Web API优雅停机和Worker的停止逻辑,完全不需要手动处理生命周期绑定。

方案2:Worker内部控制Web应用启动(特殊场景使用)

如果因为特殊业务逻辑必须在Worker类内部控制Web应用的启动时机,不要使用同步的_app.Run()方法,改用RunAsync绑定服务停止令牌,同时将Web应用启动作为独立异步任务执行,避免阻塞后台循环。
修改后的Worker代码如下:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IService _service;
    private WebApplication? _app;

    public Worker(ILogger<Worker> logger, IService service)
    {
        _logger = logger;
        _service = service;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await _service.StartAsync().ConfigureAwait(false);

        // 构建Web主机,复用当前应用的配置和路径
        var builder = WebApplication.CreateBuilder(new WebApplicationOptions
        {
            ContentRootPath = AppContext.BaseDirectory,
            EnvironmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
        });
        // 注意:如果需要复用Worker已注入的服务实例,需要手动将服务同步到builder.Services集合,避免重复构建实例
        builder.RegisterWeb();
        _app = builder.Build();
        _app.ConfigureWebApplication(builder.Environment);

        // 异步启动Web主机,传入停止令牌,服务停止时自动触发Web主机停机,不要await该任务避免阻塞
        var webRunTask = _app.RunAsync(stoppingToken);

        // 原有后台循环逻辑
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken).ConfigureAwait(false);
        }

        // 等待Web主机完成停机流程
        await webRunTask.ConfigureAwait(false);
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        // 优先停止Web主机
        if (_app != null)
        {
            await _app.StopAsync(stoppingToken).ConfigureAwait(false);
            await _app.DisposeAsync().ConfigureAwait(false);
        }
        await _service.StopAsync().ConfigureAwait(false);
        await base.StopAsync(stoppingToken).ConfigureAwait(false);
    }
}

注意:该方案需要手动处理服务注册同步、配置复用、生命周期绑定逻辑,容易出现服务实例重复、配置不生效的问题,非特殊场景优先选择方案1。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.15 16:16:02