题
假设我有一个实现 IDisposable 接口的类。像这样:
MyClass 使用一些非托管资源,因此 IDisposable 中的 Dispose()方法会释放这些资源。 MyClass 应该像这样使用:
using ( MyClass myClass = new MyClass() ) {
myClass.DoSomething();
}
现在,我想实现一个异步调用 DoSomething()的方法。我向 MyClass 添加了一种新方法:
现在,从客户端来看,应该像这样使用 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 真正做的事情。
解决方案 6
所以,我的想法是保持有多少 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();
当在使用子句之外调用 DoSomething()时,预期的行为应该是启动 ObjectDisposedException 。但我发现这不足以重新考虑这个解决方案。
其他提示
看起来您正在使用基于事件的异步模式(请参阅此处有关.NET异步模式的更多信息)所以你通常拥有的是在异步操作完成时触发的类上的事件,名为DoSomethingCompleted
(注意AsyncDoSomething
应该被称为DoSomethingAsync
>正确地遵循模式)。有了这个事件,你可以写:
var myClass = new MyClass();
myClass.DoSomethingCompleted += (sender, e) => myClass.Dispose();
myClass.DoSomethingAsync();
另一种方法是使用IAsyncResult
模式,您可以将调用dispose方法的委托传递给AsyncCallback
参数(有关此模式的更多信息也在上面的页面中)。在这种情况下,您将使用BeginDoSomething
和EndDoSomething
方法而不是<=>,并将其称为......
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时,它将拥有它需要执行的所有数据的副本。该方法应负责清理所有资源。
您可以添加回调机制并将清理函数作为回调传递。
var x = new MyClass();
Action cleanup = () => x.Dispose();
x.DoSomethingAsync(/*and then*/cleanup);
但如果你想在同一个对象实例上运行多个异步调用,这会产生问题。
一种方法是使用计算信号量 href =“http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx”rel =“nofollow noreferrer”>信号量类,用于计算正在运行的异步作业的数量。
将计数器添加到MyClass并在每个AsyncWhatever调用增加计数器时,退出它。当信号量为0时,该类就可以处理了。
var x = new MyClass();
x.DoSomethingAsync();
x.DoSomethingAsync2();
while (x.RunningJobsCount > 0)
Thread.CurrentThread.Sleep(500);
x.Dispose();
但我怀疑这是理想的方式。我闻到了一个设计问题。也许重新考虑MyClass设计可以避免这种情况?
你能分享一些MyClass实现吗?该怎么办?
我认为很遗憾微软没有要求IDisposable
合同的一部分,实现应该允许从任何线程上下文调用Dispose
,因为没有理智的方法创建一个对象可以强制继续存在创建它的线程上下文。可以设计代码,以便创建对象的线程能够以某种方式监视对象变得过时,并且可以在方便的时候Control.BeginInvoke
,并且当线程不再需要其他任何东西时它将一直存在,直到所有适当的对象已经List<>
d,但我不认为有一种标准机制不需要创建<=>的线程部分的特殊行为。
你最好的选择可能是在一个共同的线程(也许是UI线程)中创建所有感兴趣的对象,尽量保证线程在感兴趣的对象的生命周期内保持不变,并使用像< =>请求对象的处置。如果对象创建和清理都不会阻塞任何时间长度,这可能是一个很好的方法,但如果任何一个操作可能阻止可能需要一个不同的方法[也许打开一个隐藏的虚拟形式与自己的线程,所以一个人可以使用<=>那里。
或者,如果您可以控制<=>实现,请对它们进行设计,以便可以安全地异步触发它们。在许多情况下,那将<!>“只是工作<!>”;如果没有人试图在处理时使用该物品,但这不是特定的。特别是,对于许多类型的<=>,多个对象实例可能都操纵共同的外部资源[例如,对象可以包含<=>创建的实例,在构造实例时将实例添加到该列表,并删除<=>;如果列表操作未同步,则异步<=>可能会破坏列表,即使正在处理的对象未在其他方式使用。
BTW,一个有用的模式是让对象在使用时允许异步处理,期望这样的处理会导致正在进行的任何操作在第一个方便的机会抛出异常。像套接字这样的东西就是这样的。读取操作可能无法在没有将其套接字处于无用状态的情况下提前退出,但如果套接字永远不会被使用,那么如果另一个线程已经确定,那么读取就没有必要继续等待数据它应该放弃。恕我直言,这就是所有<=>对象应该努力表现的方式,但我知道没有文件要求这样的一般模式。