如何异步处理?

时间:2008-12-30 12:14:26

标签: c# asynchronous dispose

假设我有一个实现 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();
}

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

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

感谢。

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


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

using ( MyClass myClass = new MyClass() ) {

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

}

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

9 个答案:

答案 0 :(得分:11)

看起来您正在使用基于事件的异步模式(see here for more info about .NET async patterns),因此您通常拥有的是在异步操作完成时触发的类上的事件,名为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);        

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

答案 1 :(得分: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();
    }
});

答案 2 :(得分:2)

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

答案 3 :(得分:2)

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

var x = new MyClass();

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

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

但如果你想在同一个对象实例上运行多个异步调用,这会产生问题。

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

将计数器添加到MyClass并在每个AsyncWhatever调用增加计数器时,退出它。当信号量为0时,该类就可以处理了。

var x = new MyClass();

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

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

x.Dispose();

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

你能分享一些MyClass实现吗?该怎么办?

答案 4 :(得分:2)

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

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

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

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

答案 5 :(得分: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();

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

答案 6 :(得分:1)

我不得不上过老学校。不,您不能使用简化的“ using”块。但是Using块只是语法糖,用于清除半复杂的try / catch / finally块。像处理其他任何方法一样,构建您的处置,然后在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();
        }
    }

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

答案 7 :(得分:1)

在这个老问题上,这是更现代的说法。

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

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

答案 8 :(得分:0)

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

using System.Threading.Tasks;

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

Here 是对 Microsoft 官方文档的引用。