.NET Core IHostedService 后台任务异常不会终止应用程序。

17
我有一个需要在 IHostedService 后台任务遇到某种情况时终止的程序。我希望通过在后台任务中抛出异常来实现这一点,该异常会被传递到主函数。然后我可以触发取消令牌以终止其他后台任务。
我的问题是,当我抛出异常时,它只会停止该任务,而其他部分仍然在运行。是否有办法做到这一点,或者有更好的方法来实现我想要的功能?是否有另一种方式让后台任务触发常规的 CancellationToken?
我在下面的代码中包含了我问题的简化版本。如果我注释掉 await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken); 这行代码,那么异常就会按预期执行,并且我可以触发 CancellationToken。但是如果保留该行代码,则该任务会停止,但程序不会停止。
注意:在我混乱的代码中,我有更多的 IHostedServices 在运行,这就是为什么我试图触发 cancelSource.Cancel() 的原因。
public class Program
{
    public static async Task Main(string[] args)
    {
        using (var cancelSource = new CancellationTokenSource())
        {
            try
            {
                await new HostBuilder()
                    .ConfigureServices((hostContext, services) =>
                    {
                        services.AddHostedService<TestService>();
                    })
                    .Build()
                    .RunAsync(cancelSource.Token);
            }
            catch (Exception E)
            {
                cancelSource.Cancel();
            }
        }
    }
}
public class TestService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {

        while (!stoppingToken.IsCancellationRequested)
        {
            await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
            Console.WriteLine("loop 1");
            throw new ApplicationException("OOPS!!");
        }
    }
}
1个回答

28

ExecuteAsync方法中使用await操作符来注释唯一的行将使代码同步运行。如果我们查看BackgroundService.StartAsync源代码,可以看到它检查_executingTask.IsCompleted,并返回一个任务,该任务将包含您的异常,以防ExecuteAsync方法中没有任何等待,否则它将返回Task.CompletedTask,您将无法从Main方法中捕获此异常。

您可以通过在所有后台服务中注入的IApplicationLifetime来管理您的服务。例如,您可以在ExecuteMethod中捕获异常并调用ApplicationLifetime.StopApplication

示例:

static async Task Main(string[] args)
{
    await new HostBuilder()
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<TestService>();
            services.AddHostedService<TestService2>();
        })
        .Build()
        .RunAsync();

    Console.WriteLine("App stoped");
}

服务 1

public class TestService : BackgroundService
{
    private readonly IApplicationLifetime _applicationLifetime;
    public TestService(IApplicationLifetime applicationLifetime)
    {
        _applicationLifetime = applicationLifetime;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!_applicationLifetime.ApplicationStopping.IsCancellationRequested)
            {
                await Task.Delay(TimeSpan.FromSeconds(1), _applicationLifetime.ApplicationStopping);
                Console.WriteLine("running service 1");
                throw new ApplicationException("OOPS!!");
            }
        }
        catch (ApplicationException)
        {
            _applicationLifetime.StopApplication();
        }
    }
}

服务2

public class TestService2 : BackgroundService
{
    private readonly IApplicationLifetime _applicationLifetime;
    public TestService2(IApplicationLifetime applicationLifetime)
    {
        _applicationLifetime = applicationLifetime;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!_applicationLifetime.ApplicationStopping.IsCancellationRequested)
            {
                await Task.Delay(100, _applicationLifetime.ApplicationStopping);
                Console.WriteLine("running service 2");
            }
        }
        catch (ApplicationException)
        {
            _applicationLifetime.StopApplication();
        }
    }
}

输出:

running service 2
running service 2
running service 1
App stoped

6
这个答案正是我所寻找的,但似乎 IApplicationLifetime 已被弃用,推荐使用 IHostApplicationLifetime - cudima

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接