实施可iDisposable和iasyncdisposable

发布于 2025-02-12 01:01:44 字数 1052 浏览 2 评论 0原文

假设我有一个不处理任何不受管理资源的非密封班。我需要在处置阶段进行单个异步电话,以进行一些清理。没有其他托管资源可以处理。

据我了解,为了进行异步清理调用,我必须实现IASYNCDISPOSABLE,并使用DispoSeasync()和Disposeasynccore()方法。但是该指南说,当您实施异步处置模式时,您还应该实施处置模式。这一切都很好,但是在Dispose()中我没有什么需要做的。

因此,我的问题是,distose()逻辑是空的还是我需要以同步的方式进行异步清理的操作? (请参阅“如果有什么要去的话”中的代码中的评论)。

public class MyClass : IDisposable, IAsyncDisposable
{
    private bool disposed;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);

        Dispose(false);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // What if anything should go here?
            }

            disposed = true;
        }
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        // Make async cleanup call here e.g. Database.CleanupAsync();
    }
}

Say I have a non-sealed class that does not deal with any unmanaged resources. I need to make a single async call during its disposing stage to do some clean up. There are no other managed resources to deal with.

From what I understand, in order to make the async clean up call, I must implement IAsyncDisposable and use the DisposeAsync() and DisposeAsyncCore() methods. But the guidance says that you should also implement the dispose pattern when you implement the async dispose pattern. This is all fine but there's nothing really I need to do in the Dispose().

So my question is, should the Dispose() logic be empty or do I need something to do the async cleanup in a synchronous way? (see comment in code about "What if anything should go here").

public class MyClass : IDisposable, IAsyncDisposable
{
    private bool disposed;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);

        Dispose(false);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // What if anything should go here?
            }

            disposed = true;
        }
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        // Make async cleanup call here e.g. Database.CleanupAsync();
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

jJeQQOZ5 2025-02-19 01:01:44

为那些仍然不愿实现这两者的人的示例:

internal class Program
{
    static void Main(string[] args)
    {
        foreach (var a in new B()){}
        //IAsyncDisposable is not called - you leaking resources. 
        //No deadlocks in UI, no warning in compilation, nothing.
        //So it is better to be on safe side and implement both
        //because you never know how one will manage lifetime of your class.
    }

    public class B : IEnumerable, IAsyncEnumerable<object>
    {
        public IEnumerator GetEnumerator() => new A();
        public IAsyncEnumerator<object> GetAsyncEnumerator(CancellationToken ct) => new A();
    }

    public class A : IAsyncEnumerator<object>, IEnumerator
    {
        public ValueTask DisposeAsync()
        {
            Console.WriteLine("Async Disposed");
            return ValueTask.CompletedTask;
        }

        public bool MoveNext() => false;
        public void Reset(){}
        public ValueTask<bool> MoveNextAsync() => ValueTask.FromResult(false);

        public object Current => null;
    }
}

结论

您只能自由添加对异步版本的支持,但要当心:某些包装,例如foreach或旧版本的Di容器( ninject structuremap 等),诸如 retssharp 之类的代码生成器,或诸如 castle.proxy的代理生成器可能不支持iasyncdisposable。无法将对象投放到iDisposable将很难捕获您的应用中的错误。而如果您确实实施了它,那么可能发生的最糟糕的事情是僵局,最后阻止(如果您通过Sync-over-Async进行)。

通常,最好支持这两个操作,如果您打算公开API,或者您无法控制一生(例如在DI容器或其他知名的包装器中)。

如何

有完整的Microsoft示例,介绍了如何在可继承的类中实现它们两个(非密封,例如在您的示例中) - https://learn.microsoft.com/en-en-us/ dotnet/standard/standard/垃圾收集/实现disposeasync#enasul both-dispose and-asspose-dispose-patterns

class ExampleConjunctiveDisposableusing : IDisposable, IAsyncDisposable
{
    IDisposable? _disposableResource = new MemoryStream();
    IAsyncDisposable? _asyncDisposableResource = new MemoryStream();

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);

        Dispose(disposing: false);
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
        GC.SuppressFinalize(this);
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _disposableResource?.Dispose();
            (_asyncDisposableResource as IDisposable)?.Dispose();
            _disposableResource = null;
            _asyncDisposableResource = null;
        }
    }
    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (_asyncDisposableResource is not null)
        {
            await _asyncDisposableResource.DisposeAsync().ConfigureAwait(false);
        }

        if (_disposableResource is IAsyncDisposable disposable)
        {
            await disposable.DisposeAsync().ConfigureAwait(false);
        }
        else
        {
            _disposableResource?.Dispose();
        }

        _asyncDisposableResource = null;
        _disposableResource = null;
    }
}

Example to those who still hesitate to implement both:

internal class Program
{
    static void Main(string[] args)
    {
        foreach (var a in new B()){}
        //IAsyncDisposable is not called - you leaking resources. 
        //No deadlocks in UI, no warning in compilation, nothing.
        //So it is better to be on safe side and implement both
        //because you never know how one will manage lifetime of your class.
    }

    public class B : IEnumerable, IAsyncEnumerable<object>
    {
        public IEnumerator GetEnumerator() => new A();
        public IAsyncEnumerator<object> GetAsyncEnumerator(CancellationToken ct) => new A();
    }

    public class A : IAsyncEnumerator<object>, IEnumerator
    {
        public ValueTask DisposeAsync()
        {
            Console.WriteLine("Async Disposed");
            return ValueTask.CompletedTask;
        }

        public bool MoveNext() => false;
        public void Reset(){}
        public ValueTask<bool> MoveNextAsync() => ValueTask.FromResult(false);

        public object Current => null;
    }
}

Conclusion

You can freely add support for async version only, but beware: some wraps, like foreach or older versions of DI containers (Ninject, StructureMap, etc), code generators like RestSharp, or proxy generators like Castle.Proxy might not support IAsyncDisposable. Failing to cast object to IDisposable will present hard to catch bugs in your app. Whereas if you do implement it, the worst thing that could happen is deadlock in finally block (if you do it through sync-over-async).

In general, it is better to support both operations if you plan to make it public API or you don't have control over your class lifetime (like in DI containers or other widely known wrappers).

How to

There is full Microsoft example on how to implement both of them in inheritable class (non-sealed, like in your example) - https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#implement-both-dispose-and-async-dispose-patterns

class ExampleConjunctiveDisposableusing : IDisposable, IAsyncDisposable
{
    IDisposable? _disposableResource = new MemoryStream();
    IAsyncDisposable? _asyncDisposableResource = new MemoryStream();

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);

        Dispose(disposing: false);
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
        GC.SuppressFinalize(this);
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _disposableResource?.Dispose();
            (_asyncDisposableResource as IDisposable)?.Dispose();
            _disposableResource = null;
            _asyncDisposableResource = null;
        }
    }
    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (_asyncDisposableResource is not null)
        {
            await _asyncDisposableResource.DisposeAsync().ConfigureAwait(false);
        }

        if (_disposableResource is IAsyncDisposable disposable)
        {
            await disposable.DisposeAsync().ConfigureAwait(false);
        }
        else
        {
            _disposableResource?.Dispose();
        }

        _asyncDisposableResource = null;
        _disposableResource = null;
    }
}
一场春暖 2025-02-19 01:01:44

处置功能的两种实现都是从呼叫者的角度来看。然后,您的班级将提供两种机制,以消除任何托管和未管理的资源,而呼叫者的应用程序决定选择什么。这也确保了任何无法使用异步模式的消费者不会丢失。

如果您确定或想强制班级的异步消费,则实际上不需要实现同步处置。

因此,根据您对班级使用的愿景,您可以选择如何处置对象。如果您选择保留这两种机制,则可以双向处理所有资源。

Both implementations of dispose feature is from the callers point of view. Your class would then offer both mechanisms to dispose off any managed and unmanaged resources and the caller application decides what to choose. This also ensures that any consumer which is unable to make use of asynchronous patterns are not lost.

You do not really need to implement synchronous dispose if you are sure about or want to force asynchronous consumption of your class.

So depending on your vision of class usage, you can choose how to dispose objects. If you choose to keep both mechanisms, you can dispose all resources both ways.

2025-02-19 01:01:44

正如您所说,该课程是未密封的。对于密封的类,足以实现i(async)一次性接口。存在一次性模式存在,因为派生类可能要添加可以同步或异步的清理逻辑。你不知道。这就是为什么您需要实现同步和异步案例的整个模式。
对于您的问题。切勿在同步中阻止异步调用DISPOSE方法。正确使用班级是呼叫者的责任。如果他决定调用Distose而不是disposeasync,并且仅清除同步资源,那是他的决定/错误。如果此异步调用disposeasync对于适当的清理绝对必要,并且由您控制,请考虑添加同步等效词以在dispose方法中使用。

As you have said, the class is non-sealed. For sealed classes, it's enough to implement I(Async)Disposable interface. The Disposable pattern exists because the derived class may want to add cleanup logic that can be either sync or async. You can't know. That's why you need to implement the whole pattern for sync and async cases.
For your question. Never block async call in sync Dispose method. It's a caller's responsibility to use your class correctly. If he decides to call Dispose instead of DisposeAsync and clear only sync resources it's his decision/mistake. If this async call in DisposeAsync is absolutely necessary for proper cleanup and it is controlled by you, consider adding sync equivalent to be used in Dispose method.

浅暮の光 2025-02-19 01:01:44

给任何与我缺乏知识的人的注释...

我遇到了一个相同的问题,我只想实现 iasyncdisposable 在中使用中进行异步HTTP调用>最后要完成的块,但是使用(等待MyAsyncDisposable())错误地使用了同步。该解决方案正在使用等待(等待MyAsyncDisposable()),而IASyncDisposable是唯一需要的iasyncdisposable。

A note for anyone with the same lack of knowledge as me...

I had the same issue in which I only wanted to implement IAsyncDisposable to do an asynchronous HTTP call within a using block to be done at the end, but was wrongly using the synchronous using (await MyAsyncDisposable()). The solution was on using await using (await MyAsyncDisposable()) and that way the IAsyncDisposable is the only one needed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文