前言
上次,我们实现了《使用“装饰者模式”捕获 BackgroundService 中的异常》。
结果发现,微软已经发现了这个问题,并在 .NET 6 中解决了。(囧)
让我们验证一下:
using IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<DemoBackgroundService>();
})
.Build();
await host.RunAsync();
public class DemoBackgroundService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Console.WriteLine("DemoBackgroundService.ExecuteAsync");
await Task.Delay(5000);
throw new Exception("DemoBackgroundService throw Exception");
}
}
确实会抛出异常并终止程序:
那现在让我们学习下,微软官方是如何实现的,对我们以后处理类似问题可以起到借鉴作用。
实现代码
通过查看提交历史[1]。我们发现如下修改:
BackgroundService
BackgroundService[2] 并没有较大修改,只是将 _executingTask
改名为 _executeTask
,并暴露出去:
/// <summary>
/// Gets the Task that executes the background operation.
/// </summary>
/// <remarks>
/// Will return <see langword="null"/> if the background operation hasn't started.
/// </remarks>
public virtual Task ExecuteTask => _executeTask;
Host
关键改动是在 Host[3]
原来是仅仅启动了 IHostedService
:
foreach (IHostedService hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
}
现在在启动了 IHostedService
后,还会对 BackgroundService.ExecuteTask
进行try-catch
:
foreach (IHostedService hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
if (hostedService is BackgroundService backgroundService)
{
_ = TryExecuteBackgroundServiceAsync(backgroundService);
}
}
private async Task TryExecuteBackgroundServiceAsync(BackgroundService backgroundService)
{
Task backgroundTask = backgroundService.ExecuteTask;
...
try
{
await backgroundTask.ConfigureAwait(false);
}
catch (Exception ex)
{
...
if (_options.BackgroundServiceExceptionBehavior == BackgroundServiceExceptionBehavior.StopHost)
{
_logger.BackgroundServiceStoppingHost(ex);
_applicationLifetime.StopApplication();
}
}
}
关键之处在于,执行TryExecuteBackgroundServiceAsync
时并没有加上await
,也就不会阻塞后续代码的执行;但在方法内部使用了await backgroundTask
等待 ExecuteTask 执行完成。所以当 ExecuteTask
出错时,能够截获错误并执行终止当前应用程序操作。
参考资料
[1]
提交历史: https://github.com/dotnet/runtime/pull/50569/files#diff-47e4bfc6ce357641a2b2f5e94e6981fb5ceadcb8e22d67060564b5ed5f44ceb0
[2]BackgroundService
: https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs
Host
: https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs
添加微信号【MyIO666】,邀你加入技术交流群