异步可释放对象创建。

6

Disposable.Create需要一个Action作为参数。当 Rx 订阅被取消订阅时,将运行该Action

在取消 Rx 订阅时,我想执行一些异步清理代码,但是使用async () =>Action相同,这等同于async void,我想避免这种情况。关于为什么我要避免这种情况的更多细节,请参见这里

是否可以创建类似于Disposable.AsyncCreate的东西,它接受Func<Task>而不是Action。如果可以,我应该如何将其与CompositeDisposable一起使用?

还是有其他处理异步释放的模式吗?


2
IDisposable接口没有返回任务的方法,那么调用Dispose应该如何等待任务? - Peter Bons
@Peter,这也是我的理解。不过我还在想是否有办法无论如何实现这个目标。 - 1iveowl
@JasperHBojsen - 你为什么要避免使用 async () => - Enigmativity
1
@JasperHBojsen - 它没有同样的问题。我会发布一些示例代码来展示。 - Enigmativity
1
我在我的博客上覆盖了一些模式。 - Stephen Cleary
显示剩余2条评论
2个回答

3
您可以尝试这样做。我仍然不确定这是否是一个好主意:
public class DisposableAsync
{
    private readonly IDisposable _disposable; 
    private readonly Func<Task> _asyncDisposalAction;
    public DisposableAsync(IDisposable disposable, Func<Task> asyncDisposalAction)
    {
        _disposable = disposable;
        _asyncDisposalAction = asyncDisposalAction;
    }

    public Task DisposeAsync()
    {
        _disposable.Dispose();
        return _asyncDisposalAction();
    }
}

public static class DisposableAsyncExtensions
{
    public static DisposableAsync ToAsync(this IDisposable disposable, Func<Task> asyncDisposalAction)
    {
        return new DisposableAsync(disposable, asyncDisposalAction);
    }
}

您可以像这样使用它:
async Task Go()
{

    var o = Observable.Interval(TimeSpan.FromMilliseconds(100));
    var d = o
        .Subscribe(i => Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: {i}"))
        .ToAsync(async () =>
        {
            Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Dispose Beginning");
            await Task.Delay(1000);
            Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Dispose Complete");
        });
    Console.Read();
    var t = d.DisposeAsync();
    Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Outside task, waiting for dispose to complete");
    await t;
    Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Task Complete");

}

这个解决方案无法与using()语句一起使用,而且类DisposableAsync应该更加健壮。除此之外,我想不出还有什么问题了,但我对它有点偏见,感觉有点hacky。


由于您没有使用 using,因此仍应使用 try/finally(就像使用 using 一样)来确保即使出现异常也会进行处理。 - Servy
这不完全是我想象中的解决方案,但它是我找到的最接近答案的方法。 - 1iveowl

0

我认为 async () => 并没有你想象中的问题。

试试这个:

Action x = async () =>
{
    try
    {
        Console.WriteLine("Hello");
        await Task.Delay(TimeSpan.FromSeconds(2.0));
        throw new Exception("Wait");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    Console.WriteLine("Goodbye");
};

var d = Disposable.Create(x);

d.Dispose();

它产生:

你好
等待
再见

1
我想这是因为Action内部的所有内容都被包裹在try/catch中,因此没有异常风险会被抛出到Action外部,也就不存在这样的异常会导致进程崩溃的风险。我猜这可能可行,但在我看来,这不是最直观美观的解决方案。 - 1iveowl
1
如果你将try catch从委托外移到dispose周围,你会在进程上得到一个未处理的异常。 - Shlomo
1
为了促进良好的编码实践,您是否考虑将 await 放在 try { } 体内?全面处理非 Task 返回方法和委托中的异常处理至关重要,这一点无法过分强调。 - Kirill Shlenskiy
1
@KirillShlenskiy - 我试图使代码中的错误处理部分非常明显,但通常我会按照你的建议来做。 - Enigmativity
1
不仅仅是错误处理语义的问题。现在代码在完成资源释放之前继续执行,这可能会带来问题。 - Servy

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