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

如何在C#/.NET 7中并发运行多个服务实例?

问题分析与修复方案

核心问题点

  1. SemaphoreSlim 生命周期错误
    你将SemaphoreSlim注册为Scoped,但全局并发控制需要单例实例。Scoped生命周期会导致每个服务Scope生成独立的信号量,完全无法起到全局限流的作用。

  2. 双重信号量等待
    ConsoleHostedService中已经通过_semaphore.WaitAsync()获取了许可,ScrapingService的ScrapeAsync方法中又重复执行了一次等待操作。这相当于每次执行任务需要消耗2个许可,而你的信号量初始值仅为2,最终导致同一时间只能有1个任务运行,完全达不到并行效果。

  3. 工作任务启动逻辑低效
    当前的循环逻辑是串行等待信号量后启动单个任务,无法持续保持最大并发数。当一个任务完成后,才会触发下一次循环,中间存在空窗期。


修复代码

1. 修正SemaphoreSlim的注册为Singleton

public static IServiceCollection AddScraperServices(this IServiceCollection services)
{
    services.AddTransient<IScrapingService, ScrapingService>();
    // 改为Singleton,全局共享一个信号量实例
    services.AddSingleton(provider => new SemaphoreSlim(2));
    return services;
}

2. 移除ScrapingService中的信号量操作

public class ScrapingService : IScrapingService
{
    // 保留必要依赖,移除SemaphoreSlim注入
    // .. properties and constructor here

    public async Task ScrapeAsync()
    {
        // 移除此处的WaitAsync和Release调用
        // do your scraping stuff
    }
}

3. 优化ConsoleHostedService的工作任务逻辑

internal sealed class ConsoleHostedService : IHostedService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly SemaphoreSlim _semaphore;
    private readonly ILogger<ConsoleHostedService> _logger;
    private readonly IHostApplicationLifetime _appLifetime;
    private readonly List<Task> _workerTasks = new();

    public ConsoleHostedService(IServiceProvider serviceProvider, SemaphoreSlim semaphore, ILogger<ConsoleHostedService> logger, IHostApplicationLifetime appLifetime)
    {
        _serviceProvider = serviceProvider;
        _semaphore = semaphore;
        _logger = logger;
        _appLifetime = appLifetime;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _appLifetime.ApplicationStarted.Register(() =>
        {
            // 启动与信号量许可数一致的工作任务
            for (int i = 0; i < 2; i++)
            {
                _workerTasks.Add(Task.Run(async () =>
                {
                    while (!cancellationToken.IsCancellationRequested)
                    {
                        await _semaphore.WaitAsync(cancellationToken);
                        try
                        {
                            using var scope = _serviceProvider.CreateScope();
                            var scrapingService = scope.ServiceProvider.GetRequiredService<IScrapingService>();
                            await scrapingService.ScrapeAsync();
                        }
                        catch (Exception ex)
                        {
                            _logger.LogError(ex, "抓取任务发生未处理异常");
                        }
                        finally
                        {
                            _semaphore.Release();
                        }
                    }
                }, cancellationToken));
            }
        });

        return Task.CompletedTask;
    }

    // 可选:实现StopAsync确保工作任务优雅停止
    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await Task.WhenAll(_workerTasks);
    }
}

修复说明

  • Singleton信号量:确保所有工作任务共享同一个并发控制实例,准确限制全局并发数。
  • 移除双重等待:让ConsoleHostedService统一负责并发控制,避免许可被重复消耗。
  • 固定数量工作任务:启动与信号量许可数相同的任务,每个任务循环执行抓取逻辑。当一个任务完成释放许可后,会立即进入下一次循环,始终保持最大并发数,不会出现空窗期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:53:08