如何处理具有异步方法调用的对象?

16

我有一个实现了IDisposable的对象PreloadClient,我想要对其进行处理,但是在异步方法完成调用之后却没有发生处理...

    private void Preload(SlideHandler slide)
    {
        using(PreloadClient client = new PreloadClient())
        {                 
             client.PreloadCompleted += client_PreloadCompleted;
             client.Preload(slide);
        }
        // Here client is disposed immediately
    }
    private void client_PreloadCompleted(object sender, SlidePreloadCompletedEventArgs e)
    {
     // this is method is called after a while, 
     // but errors are thrown when trying to access object state (fields, properties)
    }

那么,有什么想法或解决方法吗?

7个回答

11
  1. You shouldn't use the using construct, but rather dispose your objects when they are no longer needed:

    // keep a list of strong references to avoid garbage collection,
    // and dispose them all in case we're disposing the encapsulating object
    private readonly List<PreloadClient> _activeClients = new List<PreloadClient>();
    private void Preload(SlideHandler slide)
    {
        PreloadClient client = new PreloadClient();
        _activeClients.Add(client);
        client.PreloadCompleted += client_PreloadCompleted;
        client.Preload(slide);
    }
    
    private void client_PreloadCompleted(object sender,
         SlidePreloadCompletedEventArgs e)
    {
        PreloadClient client = sender as PreloadClient;
    
        // do stuff
    
        client.PreloadCompleted -= client_PreloadCompleted;
        client.Dispose();
        _activeClients.Remove(client);
    }
    
  2. in this case, you have to dispose all clients when disposing the main class:

    protected override Dispose(bool disposing)
    {
        foreach (PreloadClient client in _activeClients)
        { 
            client.PreloadCompleted -= client_PreloadCompleted;
            client.Dispose();
        }
        _activeClients.Clear();
        base.Dispose(disposing);
    }
    
  3. Note that this implementation is not thread safe

    • Access to the _activeClients list must be made thread-safe, as your PreloadCompleted method is called from a different thread
    • Your containing object may be disposed before a client fires the event. In that case "do stuff" should do nothing, so this is another thing you should take care of.
    • It might be a good idea to use a try/finally block inside your event handler, to make sure that the object gets disposed in all cases

1
我相信这是一个很好的答案。 - Jalal El-Shaer
一个看起来适用的模式,而且我希望看到它更多地被使用,就是让客户端包含一个 IsDisposed 属性,让 _activeClients 持有客户端列表中 WeakReference 的一个列表,并在将项目添加到 _activeClients 时检查几个项,以查看是否已死亡的 WeakReference 或目标已释放,如果是,则“交换”下一项。这将避免在异步回调中需要锁定列表,并且还会用 O(1)的额外成本代替从列表中删除每个项目的 O(N)成本。 - supercat
1
@niico你只能在单个块中使用using,即单个子句。这意味着对象将在using块结束时被释放。在这种情况下,OP调用了一个异步的Preload方法,所以他希望在所有异步工作完成之前延迟释放。 - vgru

3
为什么不在回调函数中处理客户端的释放?

1
如果你有两个或更多的事件...你会选择哪一个来处理呢? - Jalal El-Shaer
@jalchr:如果您正确实现了“Dispose”,则无论调用多少次“Dispose”,多次调用“Dispose”都不会影响结果。 - dance2die
1
如果您需要在最后一个回调触发时处理对象,请使用一些锁定或互锁保护字段来跟踪有多少个回调未完成,并让每个回调检查是否为最后一个,如果是,则进行处理。 - supercat

1

我有几个想法:

  1. 更改你的架构。
  2. 在处理程序中进行处理。
  3. 使用EventWaitHandle。

请查看有关EventWaitHandle的内容:http://wekempf.spaces.live.com/Blog/cns!D18C3EC06EA971CF!672.entry?sa=571623833 - Jalal El-Shaer

0
如果有事件处理程序被注册,那么在对象上存在可能会调用这些事件时,你不可能真正地进行处置。最好的方法是使包含类可处置并将客户端存储在类变量中,在包含类被处置时对其进行处置。
可以像下面这样实现:
class ContainingClass : IDisposable
{
    private PreloadClient m_Client;

    private void Preload(SlideHandler slide)
    {
         m_Client = new PreloadClient())

         m_Client.PreloadCompleted += client_PreloadCompleted;
         m_Client.Preload(slide);

    }
    private void client_PreloadCompleted(object sender, SlidePreloadCompletedEventArgs e)
    {
    }

    public void Dispose()
    {
        if (m_Client != null)
            m_Client.Dispose();
    }
}

不好的想法。现在你把包含类的生命周期与客户端的生命周期绑定在一起了。如果 Preload 被调用超过一次怎么办?你不会处理客户端。如果在异步操作完成之前在容器上调用 dispose 呢?你很可能会得到丑陋的异常。 - Krzysztof Kozmic

0

好的,处理对象用于释放资源,这些资源在GC(最终)来收集您的对象之前不希望被保留。您的dispose方法是否会杀死您在client_PreloadCompleted中需要的任何内容?

当所有预期的回调发生时,您可以使对象自行处理:为每个预期回调保留“引用计数器”,并在每个发生的回调上递减该计数器-在回调处理程序结束时检查null并进行处理。

其他解决方法:不要担心IDisposable。 GC将收集您的对象。您可能不希望回调处理程序(可能不会触发)具有关键状态。它(回调)应该在调用时打开它所需的任何资源,然后关闭它们。


0

异步等待和确定性处理并不很搭配。如果你能找到一种方法,将可处理的部分放在一个类中,将事件放在另一个类中,那么这会让一切变得简单。


请您能详细说明一下...您所说的将事件放在另一个类中是什么意思? - Jalal El-Shaer
考虑一个假想的类,它持有需要确定性清理的状态。这个类是IDisposable的一个很好的候选者。然而,如果你在它上面调用一个异步方法,那么你必须等待异步方法完成后才能处理它。为什么要麻烦呢?你可以将其变成同步方法,并在方法完成时处理对象。你在相同的时间内完成了同样的事情。 - Christian Hayter

0
为什么不在client_PreloadCompleted方法中进行处理呢? 类似于thecoop提供的方式,只是将Dispose调用放在上述方法内,在从客户端对象内访问所有所需数据后进行处理。
编辑:我认为orialmog也提供了这种方式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接