24

假设我有一个实现IDisposable接口的类。像这样的东西:

http://www.flickr.com/photos/garthof/3149605015/

MyClass使用一些非托管资源,因此IDisposable的Dispose()方法会释放这些资源。MyClass应该像这样使用:

using ( MyClass myClass = new MyClass() ) {
    myClass.DoSomething();
}

现在,我想实现一个异步调用DoSomething()的方法。我向MyClass添加了一个新方法:

http://www.flickr.com/photos/garthof/3149605005/

现在,从客户端来看,MyClass应该像这样使用:

using ( MyClass myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

但是,如果我不做任何其他事情,这可能会失败,因为对象myClass可能在调用DoSomething()之前被释放(并抛出意外的ObjectDisposedException)。因此,对Dispose()方法的调用(隐式或显式)应该延迟到对DoSomething()的异步调用完成。

我认为Dispose()方法中的代码应该以异步方式执行,并且只有在所有异步调用都被解决后。我想知道这可能是实现这一目标的最佳方式。

谢谢。

注意:为了简单起见,我没有详细介绍 Dispose() 方法是如何实现的。在现实生活中,我通常遵循Dispose 模式


更新:非常感谢您的回复。我感谢你的努力。正如chakrit 所评论的,我需要对异步 DoSomething 进行多次调用。理想情况下,这样的事情应该可以正常工作:

using ( MyClass myClass = new MyClass() ) {

    myClass.AsyncDoSomething();
    myClass.AsyncDoSomething();

}

我会研究计数信号量,这似乎是我在寻找的东西。这也可能是设计问题。如果我觉得方便,我将与您分享一些真实案例以及MyClass的真正作用。

4

9 回答 9

11

看起来您正在使用基于事件的异步模式(有关 .NET 异步模式的更多信息,请参见此处),因此您通常拥有的是在异步操作完成时触发的类上的一个事件,命名为DoSomethingCompleted(请注意AsyncDoSomething应该真正调用DoSomethingAsync以正确遵循模式)。暴露此事件后,您可以编写:

var myClass = new MyClass();
myClass.DoSomethingCompleted += (sender, e) => myClass.Dispose();
myClass.DoSomethingAsync();

另一种选择是使用该IAsyncResult模式,您可以将调用 dispose 方法的委托传递给AsyncCallback参数(有关此模式的更多信息也在上面的页面中)。在这种情况下,您将拥有BeginDoSomethingandEndDoSomething方法而不是DoSomethingAsync, 并将其称为...

var myClass = new MyClass();
myClass.BeginDoSomething(
    asyncResult => {
                       using (myClass)
                       {
                           myClass.EndDoSomething(asyncResult);
                       }
                   },
    null);        

但是无论您采用哪种方式,您都需要一种方法来通知调用者异步操作已完成,以便它可以在正确的时间处理对象。

于 2008-12-30T12:28:43.843 回答
5

异步方法通常有一个回调,允许您在完成时执行一些操作。如果这是你的情况,它会是这样的:

// The async method taks an on-completed callback delegate
myClass.AsyncDoSomething(delegate { myClass.Dispose(); });

另一种解决方法是异步包装器:

ThreadPool.QueueUserWorkItem(delegate
{
    using(myClass)
    {
        // The class doesn't know about async operations, a helper method does that
        myClass.DoSomething();
    }
});
于 2008-12-30T12:23:08.317 回答
3

我认为不幸的是,微软没有要求作为IDisposable合同的一部分,实现应该允许Dispose从任何线程上下文中调用,因为没有理智的方法可以创建一个对象可以强制线程上下文继续存在它是创建的。可以设计代码,以便创建对象的线程以某种方式监视对象变得过时并且可以Dispose在其方便时,并且当线程不再需要其他任何东西时,它会一直存在,直到所有适当的对象都已经完成Disposed,但我认为没有一种标准机制不需要创建Dispose.

您最好的选择可能是在一个公共线程(可能是 UI 线程)中创建所有感兴趣的对象,尝试保证线程将在感兴趣的对象的生命周期内一直存在,并使用类似Control.BeginInvoke请求对象的方法' 处理。前提是对象创建和清理都不会阻塞任何时间,这可能是一个好方法,但如果任何一个操作都可以阻塞不同的方法,则可能需要[也许用自己的线程打开一个隐藏的虚拟表单,所以可以在那里使用Control.BeginInvoke]。

或者,如果您可以控制IDisposable实现,请将它们设计为可以安全地异步触发。在许多情况下,只要没有人在处理该项目时尝试使用它,这将“正常工作”,但这几乎不是给定的。特别是,对于许多类型IDisposable,存在多个对象实例可能同时操纵公共外部资源的真正危险[例如,一个对象可能持有一个List<>已创建实例,在构造它们时将实例添加到该列表中,并删除实例Dispose。如果列表操作未同步,则异步Dispose可能会破坏列表,即使正在处理的对象未在其他情况下使用。

顺便说一句,一个有用的模式是对象在使用时允许异步处置,期望这种处置将导致任何正在进行的操作在第一个方便的机会抛出异常。像套接字这样的东西就是这样工作的。可能无法提前退出读取操作而不使其套接字处于无用状态,但如果套接字无论如何都不会被使用,那么如果另一个线程已经确定,那么读取就没有必要继续等待数据了它应该放弃。恕我直言,这就是所有IDisposable对象都应该努力表现的方式,但我知道没有文件要求这种通用模式。

于 2013-01-19T18:45:52.997 回答
3

从 C#8.0 开始,您可以使用IAsyncDisposable.

using System.Threading.Tasks;

public class ExampleAsyncDisposable : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        // await DisposeAllTheThingsAsync();
    }
}

这里是微软官方文档的参考。

于 2021-08-11T12:41:24.117 回答
2

我不会以某种方式更改代码以允许异步处理。相反,我会确保在调用 AsyncDoSomething 时,它将拥有它需要执行的所有数据的副本。该方法应该负责清理所有资源。

于 2008-12-30T12:26:22.757 回答
2

您可以添加回调机制并将清理函数作为回调传递。

var x = new MyClass();

Action cleanup = () => x.Dispose();

x.DoSomethingAsync(/*and then*/cleanup);

但是如果你想对同一个对象实例运行多个异步调用,这会带来问题。

一种方法是使用Semaphore 类实现一个简单的计数信号量来计算正在运行的异步作业的数量。

将计数器添加到 MyClass 并在每次 AsyncWhatever 调用时递增计数器,在退出时递减它。当信号量为 0 时,则该类已准备好被释放。

var x = new MyClass();

x.DoSomethingAsync();
x.DoSomethingAsync2();

while (x.RunningJobsCount > 0)
    Thread.CurrentThread.Sleep(500);

x.Dispose();

但我怀疑这将是理想的方式。我闻到了设计问题。也许重新考虑 MyClass 设计可以避免这种情况?

你能分享一些 MyClass 的实现吗?它应该做什么?

于 2008-12-30T12:35:38.730 回答
2

这是对这个老问题的更现代的解释。

真正的目标是跟踪异步任务并等到它们完成......

public class MyExample : IDisposable
{
    private List<Task> tasks = new List<Task>();

    public async Task DoSomething()
    {
        // Track your async Tasks
        tasks.Add(DoSomethingElseAsync());
        tasks.Add(DoSomethingElseAsync());
        tasks.Add(DoSomethingElseAsync());
    }

    public async Task DoSomethingElseAsync()
    {
        // TODO: something else
    }

    public void Dispose()
    {
        // Block until Tasks finish
        Task.WhenAll(tasks);

        // NOTE: C# allows DisposeAsync()
        // Use non-blocking "await Task.WhenAll(tasks)"
    }
}

考虑将其转换为可重用的基类。

有时我对静态方法使用类似的模式......

public static async Task MyMethod()
{
    List<Task> tasks = new List<Task>();

    // Track your async Tasks
    tasks.Add(DoSomethingElseAsync());
    tasks.Add(DoSomethingElseAsync());
    tasks.Add(DoSomethingElseAsync());

    // Wait for Tasks to complete
    await Task.WhenAll(tasks);
}
于 2020-08-13T15:51:53.250 回答
1

所以,我的想法是保持有多少AsyncDoSomething()等待完成,并且仅在此计数达到零时才处理。我最初的做法是:

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        pendingTasks++;
        AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            if ( pendingTasks == 0 ) {
                return;
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
        caller.EndInvoke( ar );
        pendingTasks--;
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

如果两个或多个线程尝试同时读取/写入pendingTasks变量,可能会出现一些问题,因此应该使用lock关键字来防止竞争条件:

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;
    private readonly object lockObj = new object();

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        lock ( lockObj ) {
            pendingTasks++;
            AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
            caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
        }
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            lock ( lockObj ) {
                if ( pendingTasks == 0 ) {
                    return;
                }
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        lock ( lockObj ) {
            AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
            caller.EndInvoke( ar );
            pendingTasks--;
        }
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

我发现这种方法存在问题。由于资源的释放是异步完成的,这样的事情可能会起作用:

MyClass myClass;

using ( myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

myClass.DoSomething();

当在using子句之外调用DoSomething()时,预期的行为应该是启动ObjectDisposedException 。但我认为这还不足以重新考虑这个解决方案。

于 2008-12-30T12:46:48.020 回答
1

我不得不去老学校。不,您不能使用简化的“使用”块。但是 Using 块只是用于清理半复杂的 try/catch/finally 块的语法糖。像使用任何其他方法一样构建您的 dispose,然后在 finally 块中调用它。

    public async Task<string> DoSomeStuffAsync()
    {
        // used to be a simple:
        //    using(var client = new SomeClientObject())
        //    {
        //       string response = await client.OtherAsyncMethod();
        //       return response;
        //    }
        //
        // Since I can't use a USING block here, we have to go old-school
        // to catch the async disposable.
        var client = new SomeClientObject();
        try
        {
            string response = await client.OtherAsyncMethod();
            return response;
        }
        finally
        {
            await client.DisposeAsync();
        }
    }

这很丑,但它非常有效,而且比我见过的许多其他建议简单得多。

于 2020-07-15T17:13:21.713 回答