Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/wpf/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在后台线程中创建可释放对象时发生资源泄漏_C#_Wpf_Multithreading - Fatal编程技术网

C# 在后台线程中创建可释放对象时发生资源泄漏

C# 在后台线程中创建可释放对象时发生资源泄漏,c#,wpf,multithreading,C#,Wpf,Multithreading,在我的应用程序中,我在后台(线程池)线程中创建Freezable对象,冻结它们,然后在主线程上显示它们。一切都正常,只是过了一段时间,整个系统变得迟钝,应用程序最终崩溃 我已设法将问题减少到这一行: var temp = new DrawingGroup(); 如果在不同的后台(非UI)线程上频繁运行,整个系统就会变得迟钝,最终应用程序崩溃 (在我的实际应用程序中,我会在这个对象上画一些东西,冻结它,然后在主线程上显示它,但这不是重现问题的必要条件。) 复制该问题的完整代码(复制到默认的空白w

在我的应用程序中,我在后台(线程池)线程中创建
Freezable
对象,冻结它们,然后在主线程上显示它们。一切都正常,只是过了一段时间,整个系统变得迟钝,应用程序最终崩溃

我已设法将问题减少到这一行:

var temp = new DrawingGroup();
如果在不同的后台(非UI)线程上频繁运行,整个系统就会变得迟钝,最终应用程序崩溃

(在我的实际应用程序中,我会在这个对象上画一些东西,冻结它,然后在主线程上显示它,但这不是重现问题的必要条件。)

复制该问题的完整代码(复制到默认的空白wpf应用程序中):

我认为正在发生的是:

  • DrawingGroup
    是从
    DependencyObject
    派生的,
    DependencyObject
    的构造函数使用
    Dispatcher.CurrentDispatcher
    ,然后为该线程创建一个新的
    Dispatcher
  • 新的调度程序分配一些Win32资源
  • 查看Reflector中的
    HwndWrapper
    的终结代码,我认为
    HwndWrapper
    尝试使用
    Dispatcher.BeginInvoke
    同步它自己的清理
  • 由于此后台线程从不启动消息循环,因此清理代码永远不会被调用=>Resource leak
  • 有没有办法解决或解决这个问题

    到目前为止,我所尝试的:

    • 显然,使用
      线程池
      任务
      而不是手动创建线程会延迟此问题。但是
      ThreadPool
      也会随着时间的推移创建和关闭新线程,因此这只会延迟问题,而不是解决方案
    • 在每个线程的末尾强制执行完整的GC收集不会改变任何事情。这与垃圾收集的不确定性无关
    • 在后台线程结束时手动调用
      Dispatcher.InvokeShutdown
      似乎可行,但我不知道如何确保在每个
      ThreadPool
      线程结束时调用它。没有编写自己的
      线程池
      ,即

    您使用
    TPL
    ThreadPool
    运行此逻辑对吗?
    如果是这样,最后一个案例是您的一个选项,那么您可以轻松地获取
    DrawingGroup
    的属性,并在
    finally
    块中调用它的方法

    所以你可以这样写:

    DrawingGroup temp;
    try
    {
        temp = new DrawingGroup();
    }
    finally
    {
        // do the work
        if (temp != null)
        {
            temp.Dispatcher.InvokeShutdown();
        }
    }
    

    这是.NET中调度器系统设计的一个已知缺陷。它影响依赖于Dispatcher的WPF和非WPF库。微软已经表示这一问题不会得到解决

    它与冷冻操作或任何操作的使用无关。从
    DependencyObject
    派生的任何对象(类)都将具有一个基本构造函数,该构造函数将触发为该线程创建
    Dispatcher
    实例(如果以前没有创建过)。换句话说,
    Dispatcher
    在设计上是一个线程本地单例

    当足够多的
    Dispatcher
    实例泄漏时,就会发生崩溃。这意味着在应用程序的生命周期中创建和销毁了相同数量的线程,每个线程都创建了一个或多个
    DependencyObject
    。询问任何一位应用程序开发人员,他们都会说这是不常见的,虽然本身并不坏,但在设计一个需要创建和销毁许多线程的应用程序时肯定需要特别小心


    在开始之前,这里提供了一种安全的方法来查询
    调度程序
    ,如果它以前不存在,则不会导致自动创建一个调度程序

    Thread currentThread = Thread.CurrentThread;
    Dispatcher currentDispatcherOrNull = Dispatcher.FromThread(currentThread);
    


    首先,您可以在使用线程后关闭dispatcher

    Thread currentThread = Thread.CurrentThread;
    Dispatcher currentDispatcherOrNull = Dispatcher.FromThread(currentThread);
    


    其次,要意识到一旦一个线程关闭了它,就不可能为同一个线程重新初始化
    调度程序
    。换句话说,在
    InvokeShutdown
    之后,不可能在该线程上使用WPF或依赖于
    Dispatcher
    的任何其他库。这根线实际上被毒死了


    结合第一点和第二点可以得出结论,您需要自己的线程池,每个线程池都有一个
    调度程序
    。只要控制线程池的下风,就不会有泄漏的危险


    有一些流行的开源.NET线程池库,可以与.NET系统线程池并行运行(独立运行)。这是解决此特定平台问题的适当方法


    如果您同时控制前端(表示层)和后端(图像渲染),则有一种更简单、更严格、更有效的方法(尽管未充分利用):

    • 使调用者必须初始化调度程序成为策略;后端只会检查调度程序是否已经存在(通过
      dispatcher.FromThread
      ),如果没有,则拒绝执行工作
    这种方法将负担转移到表示层,具有讽刺意味的是,表示层往往已经初始化了调度器


    这种方法也适用于一个线程池的一个

    +1好主意,但我不确定它是否“合法”:线程池线程可能会再次执行相同的代码,但它不会(总是)创建一个新的
    调度程序
    ——它可能会使用“disposed”实例。我不确定什么
    DrawingGroup
    (或任何
    Freezable
    )需要一个
    调度程序
    ,但与半清理的
    调度程序
    一起使用是否有危险?您应该在不需要的时候清除
    调度程序
    Thread currentThread = Thread.CurrentThread;
    Dispatcher currentDispatcherOrNull = Dispatcher.FromThread(currentThread);