如何异步处理?

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

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

MyClass使用一些非托管资源,因此IDisposableDispose()方法会释放这些资源。 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(); } 

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

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

谢谢。

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


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

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

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

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

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

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

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

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

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

 // 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(); } }); 

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

所以,我的想法是保持有多少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 。 但我发现这不足以重新考虑这个解决方案。

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

你最好的选择可能是在一个共同的线程(也许是UI线程)中创建所有感兴趣的对象,尝试保证线程将在感兴趣的对象的生命周期中保持Control.BeginInvoke ,并使用像Control.BeginInvoke这样的东西。请求物品的处置。 如果对象创建和清理都不会阻塞任何时间长度,这可能是一个好方法,但如果任何一个操作可能阻止可能需要不同的方法[也许打开一个隐藏的虚拟表单与自己的线程,所以可以使用Control.BeginInvoke那里]。

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

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

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

 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实现吗? 该怎么办?