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

VS2022需等Quartz后台任务完成才启动Swagger浏览器问题求助

问题

我正在开发一个暴露API并使用Quartz运行后台任务的简单应用,该应用需重复执行抓取任务与检查任务。从VS2022启动应用时,Chrome要等到检查任务完成第一次迭代才会打开Swagger页面。这虽不影响核心功能但较为烦人,我可以手动打开Swagger。相关代码如下:

任务定义

public sealed class CachingBackgroundJob : IJob
{
    private readonly ISchedulerService _schedulerService;

    public CachingBackgroundJob(ISchedulerService schedulerService)
    {
        _schedulerService = schedulerService;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        await _schedulerService.FillCache(context.CancellationToken);
    }
}
public sealed class SchedulingBackgroundJob : IJob
{
    private readonly ISchedulerService _schedulerService;

    public SchedulingBackgroundJob(ISchedulerService schedulerService)
    {
        _schedulerService = schedulerService;
    }

    public async Task Execute(IJobExecutionContext context)
    {
        await _schedulerService.CheckHealth(context.CancellationToken);
    }
}

依赖注入配置

public static class DependencyInjection
{
    public static void AddScheduler(this IServiceCollection services)
    {
        services.AddQuartz(opts =>
        {
            var cachingBackgroundJobKey = JobKey.Create(nameof(CachingBackgroundJob));
            opts.AddJob<CachingBackgroundJob>(cachingBackgroundJobKey)
                .AddTrigger(trigger =>
                {
                    trigger.ForJob(cachingBackgroundJobKey)
                        .WithSimpleSchedule(schedule =>
                        {
                            schedule.WithRepeatCount(0);
                        });
                });
            var matcher = KeyMatcher<JobKey>.KeyEquals(cachingBackgroundJobKey);
            opts.AddJobListener<CachingJobCompletionListener>(matcher);
        });

        services.AddQuartzHostedService();
    }
}

任务监听器

internal class CachingJobCompletionListener : IJobListener
{
    public string Name => "CachingBackgroundJobCompletedListener";

    public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }

    public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }

    public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException, CancellationToken cancellationToken = default)
    {
        var scheduler = context.Scheduler;
        var schedulingBackgroundJobTrigger = TriggerBuilder.Create()
            .WithSimpleSchedule(schedule =>
            {
                schedule.WithIntervalInMinutes(1);
                schedule.RepeatForever();
            })
            .StartNow()
            .Build();

        var jobDetail = JobBuilder
            .Create()
            .OfType<SchedulingBackgroundJob>()
            .WithIdentity(JobKey.Create(nameof(SchedulingBackgroundJob)))
            .DisallowConcurrentExecution()
            .Build();

        await scheduler.ScheduleJob(jobDetail, schedulingBackgroundJobTrigger, cancellationToken);
    }
}

Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEnvironmentVariables();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddRouting(options => options.LowercaseUrls = true);

IConfiguration redisConfiguration = builder.Configuration.GetSection("Caching");
string connectionString = redisConfiguration["redis"]!;
builder.Services.AddSingleton<IConnectionMultiplexer>((serviceProvider) => ConnectionMultiplexer.Connect(connectionString));

ThreadPool.SetMinThreads(100, 100);

builder.Services.AddScoped<ISchedulerService, SchedulerService>();

builder.Services.AddHttpClient();
builder.Services.AddScheduler();

builder.Host.UseSerilog((context, config) =>
{
    config.ReadFrom.Configuration(context.Configuration);
});

var applicationAssembly = AppDomain.CurrentDomain.Load("Example.Application");

builder.Services
    .AddMediatR(opts => opts.RegisterServicesFromAssembly(applicationAssembly))
    .AddFluentValidationAutoValidation()
    .AddValidatorsFromAssembly(applicationAssembly);
ValidatorOptions.Global.DefaultRuleLevelCascadeMode = CascadeMode.Stop;

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseSerilogRequestLogging();
app.UseAuthorization();
app.MapControllers();

app.Run();

请问是什么原因导致VS2022自动启动浏览器的操作被阻塞?


原因分析与解决方案

核心原因

AddQuartzHostedService()的默认配置会让ASP.NET Core主机在启动时,同步等待所有Quartz初始任务执行完成,才会继续完成Web应用的启动流程。你的CachingBackgroundJob被配置为启动后立即执行一次,主机要等它执行完毕才会触发VS自动打开浏览器的操作,因此出现了阻塞现象。

具体来说,QuartzHostedService的WaitForJobsToComplete属性默认值为true,这会强制主机等待所有任务完成后才完成启动。

解决方案

修改AddQuartzHostedService的配置,将WaitForJobsToComplete设置为false,让Quartz后台任务在应用启动后异步执行,不阻塞Web应用的启动:

public static class DependencyInjection
{
    public static void AddScheduler(this IServiceCollection services)
    {
        services.AddQuartz(opts =>
        {
            var cachingBackgroundJobKey = JobKey.Create(nameof(CachingBackgroundJob));
            opts.AddJob<CachingBackgroundJob>(cachingBackgroundJobKey)
                .AddTrigger(trigger =>
                {
                    trigger.ForJob(cachingBackgroundJobKey)
                        .WithSimpleSchedule(schedule =>
                        {
                            schedule.WithRepeatCount(0);
                        });
                });
            var matcher = KeyMatcher<JobKey>.KeyEquals(cachingBackgroundJobKey);
            opts.AddJobListener<CachingJobCompletionListener>(matcher);
        });

        // 修改此处配置,禁用等待任务完成的逻辑
        services.AddQuartzHostedService(options =>
        {
            options.WaitForJobsToComplete = false;
        });
    }
}

这样修改后,Web应用启动流程不会被Quartz任务阻塞,VS会在应用启动完成后立即打开Swagger页面,而CachingBackgroundJob会在后台异步执行。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 12:34:50