【问题标题】:How to dispose objects having asynchronous methods called?如何处理调用了异步方法的对象?
【发布时间】:2013-03-27 21:48:30
【问题描述】:

我有这个对象PreloadClient,它实现了IDisposable,我想处理它,但是在异步方法完成它们的调用之后......这没有发生

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

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

【问题讨论】:

    标签: c# .net asynchronous dispose idisposable


    【解决方案1】:
    1. 您不应使用using 构造,而应在不再需要对象时将其丢弃:

      // 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. 在这种情况下,您必须在处置主类时处置所有客户端:

      protected override Dispose(bool disposing)
      {
          foreach (PreloadClient client in _activeClients)
          { 
              client.PreloadCompleted -= client_PreloadCompleted;
              client.Dispose();
          }
          _activeClients.Clear();
          base.Dispose(disposing);
      }
      
    3. 请注意,此实现不是线程安全的

      • _activeClients 列表的访问权限必须made thread-safe,因为您的PreloadCompleted 方法是从不同的线程调用的
      • 您的包含对象可能会在客户端触发事件之前被释放。在这种情况下,“做事”应该什么都不做,所以这是你应该注意的另一件事。
      • 在事件处理程序中使用 try/finally 块可能是个好主意,以确保在所有情况下都会释放对象

    【讨论】:

    • 我相信这是一个很好的答案
    • 一个似乎适用于此的模式,我希望看到更经常使用的模式,是让客户端包含IsDisposed 属性,让_activeClients 持有@ 列表987654331@ 发送给客户,并在_activeClients 中添加一个项目,检查几个项目以查看WeakReference 是否已死亡或目标已被处置,如果是,则将其与下一个项目“交换”。这将避免在异步回调中锁定列表的任何需要,并在添加项目时用 O(1) 的额外成本替换从列表中删除每个项目的 O(N) 成本。
    • @niico 您只能在单个块中使用using,即单个子句。这意味着一旦using 块结束,对象就会被释放。在这种情况下,OP 调用了一个异步 Preload 方法,因此他/她想延迟处理,直到所有异步工作完成。
    【解决方案2】:

    为什么不在回调中释放客户端?

    【讨论】:

    • 如果您有 2 个或更多事件...您选择处理哪一个 :)?
    • @jalchr:如果您正确实现了“Dispose”,那么无论“Dispose”被调用多少次,多次调用 dispose 都不会影响结果。
    • @jalchr:如果您需要在最后一个回调触发时释放一个对象,请使用一些受锁定或互锁保护的字段来跟踪有多少回调未完成,并让每个回调检查它是否最后一个,如果是,则丢弃。
    【解决方案3】:

    我有几个想法:

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

    【讨论】:

    【解决方案4】:

    如果注册了事件处理程序,那么当有可以在其上调用的事件时,您无法真正处置该对象。您最好的选择是使包含类成为一次性并将客户端存储在类变量中,以便在包含类存在时进行处置。

    类似

    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 怎么办?你很可能会遇到丑陋的异常
    【解决方案5】:

    好吧,释放对象用于杀死在 GC(最终)到来并收集您的对象之前您不希望持有的资源。你的 dispose 方法是否会在 client_PreloadCompleted 中杀死你需要的任何东西?

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

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

    【讨论】:

      【解决方案6】:

      异步等待和确定性处理不能很好地混合。如果你能找到一种拆分代码的方法,使一次性的东西放在一个类中,而事件放在另一个类中,那一切都会变得更简单。

      【讨论】:

      • 您能否详细说明...您将事件放在另一个类中是什么意思...?
      • 假设一个类持有需要确定性清理的状态。该类是 IDisposable 的一个很好的候选者。但是,如果您对其调用异步方法,则必须等待异步方法完成才能释放它。何必?您不妨将其设为同步方法,并在方法完成时释放对象。你在同样的时间内完成了同样的事情。
      【解决方案7】:

      为什么不在client_PreloadCompleted 方法中处理? 与thecoop 提供的类似,只需在上述方法中调用Dispose,在您从客户端对象内部访问所有需要的数据之后。

      编辑:我认为这也是 orialmog 提供的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-03-19
        • 2014-09-28
        • 2018-09-01
        • 2019-05-12
        • 1970-01-01
        • 2017-09-19
        • 2016-01-06
        • 2017-06-10
        相关资源
        最近更新 更多