The*_*lub 3 c# background-service task-parallel-library async-await .net-core
我正在使用.net的HostBuilder编写后台服务。我有一个名为MyService的类,该类实现BackgroundService ExecuteAsync方法,并且在那里遇到了一些奇怪的行为。在方法内部,我等待某个任务,并且吞没了在等待之后引发的任何异常,但是在等待终止过程之前引发的异常。
我在各种论坛(堆栈溢出,msdn,中等)中都在线查看,但找不到这种行为的解释。
public class MyService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(500, stoppingToken);
throw new Exception("oy vey"); // this exception will be swallowed
}
}
public class MyService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
throw new Exception("oy vey"); // this exception will terminate the process
await Task.Delay(500, stoppingToken);
}
}
Run Code Online (Sandbox Code Playgroud)
我希望这两个异常都会终止该过程
TL; DR;
不要让异常消失ExecuteAsync。处理它们,隐藏它们或显式请求关闭应用程序。
也不要等待太久才能在该处开始第一个异步操作
说明
这与其await本身无关。它引发的异常将冒泡给调用者。这是来电者,处理他们,还是不行。
ExecuteAsync是调用的方法BackgroundService,意味着方法引发的任何异常将由处理BackgroundService。该代码是:
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
Run Code Online (Sandbox Code Playgroud)
什么都没有等待返回的任务,因此这里什么也不会丢。检查IsCompleted的优化是避免在任务已经完成的情况下创建异步基础结构。
在调用StopAsync之前,不会再次检查该任务。那是任何异常都会被抛出的时候。
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
Run Code Online (Sandbox Code Playgroud)
从服务到主机
反过来,StartAsync每个服务的方法都由Host实现的StartAsync方法调用。该代码揭示了正在发生的事情:
public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();
await _hostLifetime.WaitForStartAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (var hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(cancellationToken).ConfigureAwait(false);
}
// Fire IHostApplicationLifetime.Started
_applicationLifetime?.NotifyStarted();
_logger.Started();
}
Run Code Online (Sandbox Code Playgroud)
有趣的部分是:
foreach (var hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(cancellationToken).ConfigureAwait(false);
}
Run Code Online (Sandbox Code Playgroud)
直到第一个真正的异步操作的所有代码都在原始线程上运行。遇到第一个异步操作时,将释放原始线程。await该任务完成后,一切将恢复。
从主机到Main()
Main()中用于启动托管服务的RunAsync()方法实际上是调用主机的StartAsync 而不是 StopAsync:
public static async Task RunAsync(this IHost host, CancellationToken token = default)
{
try
{
await host.StartAsync(token);
await host.WaitForShutdownAsync(token);
}
finally
{
#if DISPOSE_ASYNC
if (host is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
#endif
{
host.Dispose();
}
}
}
Run Code Online (Sandbox Code Playgroud)
这意味着从RunAsync到第一个异步操作之前的链内抛出的所有异常都将冒泡到启动托管服务的Main()调用中:
await host.RunAsync();
Run Code Online (Sandbox Code Playgroud)
要么
await host.RunConsoleAsync();
Run Code Online (Sandbox Code Playgroud)
这意味着直到对象列表中第一个实数的所有内容都在原始线程上运行。除非处理,否则抛出的任何内容都会使应用程序崩溃。由于或调用在中,因此应该放置块。awaitBackgroundServiceIHost.RunAsync()IHost.StartAsync()Main()try/catch
这也意味着,把慢的代码之前的第一个真正的异步操作可能会延迟整个应用程序。
第一个异步操作之后的所有内容将继续在线程池线程上运行。因此,在第一个操作之后引发的异常不会冒泡,直到托管服务通过调用关闭IHost.StopAsync或任何孤立的任务获得GCd为止
结论
不要让异常逃脱ExecuteAsync。抓住并妥善处理。选项是:
ExecuteAsync不会导致应用程序退出。catch块中调用StopAsync。这也将调用StopAsync所有其他后台服务文献资料
托管服务和行为BackgroundService中描述实现与IHostedService和BackgroundService类微服务后台任务,并在ASP.NET核心托管服务后台任务。
该文档没有解释如果这些服务之一抛出该怎么办。他们通过明确的错误处理演示了特定的使用方案。排队的后台服务示例将丢弃导致故障的消息,并转到下一个消息:
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
201 次 |
| 最近记录: |