C# DotNet 3.x 多个 BackgroudServices 不一致 运行

C# DotNet 3.x Multiple BackgroudServices do not run consistently

我在 DotNet 3.1 中有一个 SignalR 应用程序,有点像大型聊天应用程序,我正在尝试添加两个 BackgroundServices。

后台服务设置为 运行,只要 ASP.NET 应用 运行 就可以了。

第一个 BackgroundService 有一个非常快的主循环(50 毫秒)并且看起来运行良好。

第二个 BackgroundService 有一个更长的主循环(1000 毫秒)并且似乎随机开始,随机停止执行,然后重新开始执行......随机。这几乎就像第二个机器人要睡了很长一段时间(30 到 90 秒),然后再次醒来并保留了对象状态。

两个 BackgroundServices 具有相同的基本代码,但延迟不同。

是否可以有多个独立的、无休止的后台服务?如果是这样,那我做错了什么?

我有这样注册的服务...

_services.AddSimpleInjector(_simpleInjectorContainer, options =>
{
    options.AddHostedService<SecondaryBackgroundService>();
    options.AddHostedService<PrimaryBackgroundService>();

    // AddAspNetCore() wraps web requests in a Simple Injector scope.
    options.AddAspNetCore()
        // Ensure activation of a specific framework type to be created by
        // Simple Injector instead of the built-in configuration system.
        .AddControllerActivation()
        .AddViewComponentActivation()
        .AddPageModelActivation()
        .AddTagHelperActivation();
});

我有两个 classes (PrimaryBackgroundService/SecondaryBackgroundService) 有这个 ...

public class SecondaryBackgroundService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        await Task.Factory.StartNew(async () =>
        {
            // loop until a cancalation is requested
            while (!cancellationToken.IsCancellationRequested)
            {
                //await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken);
                await Task.Delay(TimeSpan.FromMilliseconds(1000), cancellationToken);
    
                try
                {
                    await _doWorkDelegate();
                }
                catch (Exception ex)
                {
                }
            }
    
        }, cancellationToken);
    }
}

我应该设置一个 BackgroundService 来分离两个不同的任务吗?在他们自己的线程中?我应该改用 IHostedService 吗?

我需要确保每秒第二个 BackgroundService 运行s。此外,我需要确保第二个 BackgroundService 永远不会影响更快的 运行ning 主 BackgroundService。

更新:

我按照建议更改了代码以使用定时器,但现在我正在努力从定时器事件调用异步任务。

这是我用不同的选项创建的 class,有效和无效。

// used this as the base: https://github.com/aspnet/Hosting/blob/master/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs
public abstract class RecurringBackgroundService : IHostedService, IDisposable
{
    private Timer _timer;

    protected int TimerIntervalInMilliseconds { get; set; } = 250;

    // OPTION 1. This causes strange behavior; random starts and stops
    /*
    protected abstract Task DoRecurringWork();

    private async void OnTimerCallback(object notUsedTimerState)  // use "async void" for event handlers
    {
        try
        {
            await DoRecurringWork();
        }
        finally
        {
            // do a single call timer pulse
            _timer.Change(this.TimerIntervalInMilliseconds, Timeout.Infinite);
        }
    }
    */

    // OPTION 2. This causes strange behavior; random starts and stops
    /*
    protected abstract Task DoRecurringWork();

    private void OnTimerCallback(object notUsedTimerState)
    {
        try
        {
            var tf = new TaskFactory(System.Threading.CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

            tf.StartNew(async () =>
            {
                await DoRecurringWork();
            })
            .Unwrap()
            .GetAwaiter()
            .GetResult();
        }
        finally
        {
            // do a single call timer pulse
            _timer.Change(this.TimerIntervalInMilliseconds, Timeout.Infinite);
        }
    }
    */

    // OPTION 3. This works but requires the drived to have "async void"
    /*
    protected abstract void DoRecurringWork();

    private void OnTimerCallback(object notUsedTimerState)
    {
        try
        {
            DoRecurringWork(); // use "async void" in the derived class
        }
        finally
        {
            // do a single call timer pulse
            _timer.Change(this.TimerIntervalInMilliseconds, Timeout.Infinite);
        }
    }
    */

    // OPTION 4. This works just like OPTION 3 and allows the drived class to use a Task
    protected abstract Task DoRecurringWork();

    protected async void DoRecurringWorkInternal() // use "async void"
    {
        await DoRecurringWork();
    }

    private void OnTimerCallback(object notUsedTimerState)
    {
        try
        {
            DoRecurringWork();
        }
        finally
        {
            // do a single call timer pulse
            _timer.Change(this.TimerIntervalInMilliseconds, Timeout.Infinite);
        }
    }

    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        // 
        // do a single call timer pulse
        _timer = new Timer(OnTimerCallback, null, this.TimerIntervalInMilliseconds, Timeout.Infinite);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        try { _timer.Change(Timeout.Infinite, 0); } catch {; }

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        try { _timer.Change(Timeout.Infinite, 0); } catch {; }
        try { _timer.Dispose(); } catch {; }
    }
}

选项 3 and/or 选项 4 正确吗?

我已确认选项 3 和选项 4 重叠。我怎样才能阻止它们重叠? (更新:使用选项 1)

更新

看来选项 1 还是正确的。

Stephen Cleary 是正确的。在深入研究代码之后,我确实找到了一个在 _doWorkDelegate() 方法下停止执行的任务。随机启动和停止是由失败的 HTTP 调用引起的。一旦我解决了这个问题(一劳永逸),选项 1 开始按预期工作。

我建议编写两个定时后台任务,如文档中所示

Timed background tasks documentation

那么他们就是独立和孤立的。

public class PrimaryBackgroundService : IHostedService, IDisposable
{
    private readonly ILogger<PrimaryBackgroundService> _logger;
    private Timer _timer;

    public PrimaryBackgroundService(ILogger<PrimaryBackgroundService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("PrimaryBackgroundService StartAsync");
        
        TimeSpan waitTillStart = TimeSpan.Zero;
        TimeSpan intervalBetweenWork = TimeSpan.FromMilliseconds(50);

        _timer = new Timer(DoWork, null, waitTillStart, intervalBetweenWork);

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("PrimaryBackgroundService DoWork");

        // ... do work
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("PrimaryBackgroundService is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}

使用类似的代码创建 SecondaryBackgroundService 并像以前一样注册它们

options.AddHostedService<SecondaryBackgroundService>();
options.AddHostedService<PrimaryBackgroundService>();

请注意,如果您想使用任何依赖注入,则必须将 IServiceScopeFactory 注入后台服务构造函数并调用 scopeFactory.CreateScope()