如何异步处理?

Aur*_*ron 20 c# dispose asynchronous

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

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

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

using ( MyClass myClass = new MyClass() ) {
    myClass.DoSomething();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

using ( MyClass myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢.

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


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

using ( MyClass myClass = new MyClass() ) {

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

}
Run Code Online (Sandbox Code Playgroud)

我将研究计数信号量,它似乎是我正在寻找的.这也可能是一个设计问题.如果我发现它很方便,我将与您分享一些真实案例和MyClass真正做的事情.

Gre*_*ech 11

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

var myClass = new MyClass();
myClass.DoSomethingCompleted += (sender, e) => myClass.Dispose();
myClass.DoSomethingAsync();
Run Code Online (Sandbox Code Playgroud)

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

var myClass = new MyClass();
myClass.BeginDoSomething(
    asyncResult => {
                       using (myClass)
                       {
                           myClass.EndDoSomething(asyncResult);
                       }
                   },
    null);        
Run Code Online (Sandbox Code Playgroud)

但无论你采用哪种方式,都需要一种方法让调用者知道异步操作已经完成,这样它就可以在正确的时间处理对象.


Jor*_*ers 9

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

using System.Threading.Tasks;

public class ExampleAsyncDisposable : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        // await DisposeAllTheThingsAsync();
    }
}
Run Code Online (Sandbox Code Playgroud)

这里参考了微软官方文档。


Cri*_*rdo 5

异步方法通常有一个回调,允许你在完成后做一些操作.如果这是你的情况,它将是这样的:

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

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

ThreadPool.QueueUserWorkItem(delegate
{
    using(myClass)
    {
        // The class doesn't know about async operations, a helper method does that
        myClass.DoSomething();
    }
});
Run Code Online (Sandbox Code Playgroud)


Aur*_*ron 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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

如果两个或多个线程尝试同时读/写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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

MyClass myClass;

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

myClass.DoSomething();
Run Code Online (Sandbox Code Playgroud)

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