简体   繁体   English

DisposeAsync 应该抛出后台任务异常,还是留给客户端显式观察?

[英]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这是一个要点),它允许:

  • to start a background task;启动后台任务;
  • stop this task (via a cancellation token) by calling IAsyncDisposable.DisposeAsync at any time, in a thread-safe, concurrency-friendly way;通过随时以线程安全、并发友好的方式调用IAsyncDisposable.DisposeAsync来停止此任务(通过取消令牌);
  • configure whether DisposeAsync should re-throw the task's exceptions ( DisposeAsync will await the task's completion either way, before doing a cleanup);配置DisposeAsync是否应该重新抛出任务的异常(在进行清理之前, DisposeAsync将等待任务完成);
  • the task's status, result and exceptions can be observed at any time via LongRunningAsyncDisposable.Completion property;可以通过LongRunningAsyncDisposable.Completion属性随时观察任务的状态、结果和异常;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 应该 IAsyncEnumerator<T> 如果不等待最后一个 MoveNextAsync() 任务,.DisposeAsync() 抛出? - Should IAsyncEnumerator<T>.DisposeAsync() throw if the last MoveNextAsync() task is not awaited? 返回 Task 的方法应该抛出异常吗? - Should methods that return Task throw exceptions? 如何显式地记录方法不会抛出异常 - How to explicitly document that a method does not throw exceptions 有没有不应该抛出异常的函数? - Are there any functions that should not throw exceptions? 何时以及为什么我应该显式抛出异常 - When and Why I should Explicitly throw an Exception insert方法是应该返回一个对象还是抛出异常 - Should an insert method return an object or throw exceptions 如何在Wcf服务上引发异常并在客户端上捕获它? - How to throw Exceptions on Wcf Service and catch it on client? 在我抛出异常的情况下,我应该留下无法到达的休息时间吗? - Should I leave an unreachable break in a case where I throw an exception? 存放的异常-后台任务-UWP C# - Stowed Exceptions - Background Task - UWP C# 存储库应该引发自定义异常还是返回状态类型? - Should a repository throw custom exceptions or return a status type?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM