[英]Should DisposeAsync throw background task exceptions, or leave it to the client to observe explicitly?
I don't think this question is a duplicate of "Proper way to deal with exceptions in DisposeAsync" .我不认为这个问题与"Proper way to deal with exceptions in DisposeAsync"重复。
Let's say my class that implements IAsynsDisposable
because it has a long-running background task, and DisposeAsync
terminates that task.假设我的 class 实现了
IAsynsDisposable
,因为它有一个长时间运行的后台任务,并且DisposeAsync
终止了该任务。 A familiar pattern might be the Completion
property, eg ChannelReader<T>.Completion
(despite ChannelReader
doesn't implement IAsynsDisposable
).一个熟悉的模式可能是
Completion
属性,例如ChannelReader<T>.Completion
(尽管ChannelReader
没有实现IAsynsDisposable
)。
Is it considered a good practice to propagate the Completion
task's exceptions outside DisposeAsync
?在
DisposeAsync
之外传播Completion
任务的异常是否被认为是一种好习惯?
Here is a complete example that can be copied/pasted into a dotnet new console
project.这是一个完整的示例,可以复制/粘贴到
dotnet new console
项目中。 Note await this.Completion
inside DisposeAsync
:注意 DisposeAsync 中的
await this.Completion
DisposeAsync
:
try
{
await using var service = new BackgroundService(TimeSpan.FromSeconds(2));
await Task.Delay(TimeSpan.FromSeconds(3));
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
class BackgroundService: IAsyncDisposable
{
public Task Completion { get; }
private CancellationTokenSource _diposalCts = new();
public BackgroundService(TimeSpan timeSpan)
{
this.Completion = Run(timeSpan);
}
public async ValueTask DisposeAsync()
{
_diposalCts.Cancel();
try
{
await this.Completion;
}
finally
{
_diposalCts.Dispose();
}
}
private async Task Run(TimeSpan timeSpan)
{
try
{
await Task.Delay(timeSpan, _diposalCts.Token);
throw new InvalidOperationException("Boo!");
}
catch (OperationCanceledException)
{
}
}
}
Alternatively, I can observe service.Completion
explicitly in the client code (and ignore its exceptions inside DiposeAsync
to avoid them being potentially thrown twice), like below:或者,我可以在客户端代码中显式观察
service.Completion
(并在DiposeAsync
中忽略其异常以避免它们可能被抛出两次),如下所示:
try
{
await using var service = new BackgroundService(TimeSpan.FromSeconds(2));
await Task.Delay(TimeSpan.FromSeconds(3));
await service.Completion;
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
class BackgroundService: IAsyncDisposable
{
public Task Completion { get; }
private CancellationTokenSource _diposalCts = new();
public BackgroundService(TimeSpan timeSpan)
{
this.Completion = Run(timeSpan);
}
public async ValueTask DisposeAsync()
{
_diposalCts.Cancel();
try
{
await this.Completion;
}
catch
{
// the client should observe this.Completion
}
finally
{
_diposalCts.Dispose();
}
}
private async Task Run(TimeSpan timeSpan)
{
try
{
await Task.Delay(timeSpan, _diposalCts.Token);
throw new InvalidOperationException("Boo!");
}
catch (OperationCanceledException)
{
}
}
}
Is there a concensus about which option is better?是否有关于哪个选项更好的共识?
For now, I've settled on a reusable helper class LongRunningAsyncDisposable
( here's a gist ), which allows:现在,我已经确定了一个可重用的助手 class
LongRunningAsyncDisposable
( 这是一个要点),它允许:
IAsyncDisposable.DisposeAsync
at any time, in a thread-safe, concurrency-friendly way;IAsyncDisposable.DisposeAsync
来停止此任务(通过取消令牌);DisposeAsync
should re-throw the task's exceptions ( DisposeAsync
will await the task's completion either way, before doing a cleanup);DisposeAsync
是否应该重新抛出任务的异常(在进行清理之前, DisposeAsync
将等待任务完成);LongRunningAsyncDisposable.Completion
property;LongRunningAsyncDisposable.Completion
属性随时观察任务的状态、结果和异常;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.