线程阻止所有者的垃圾收集

2023-11-26

在我创建的库中,我有一个 DataPort 类,它实现与 .NET SerialPort 类类似的功能。它与某些硬件进行通信,并且每当数据通过该硬件传入时就会引发一个事件。为了实现此行为,DataPort 启动一个线程,该线程预计具有与 DataPort 对象相同的生命周期。问题是当数据端口超出范围时,永远不会被垃圾收集

现在,由于 DataPort 与硬件通信(使用 pInvoke)并拥有一些非托管资源,因此它实现了 IDisposable。当您对对象调用 Dispose 时,一切都会正确发生。 DataPort 摆脱所有非托管资源并杀死工作线程并消失。但是,如果您只是让 DataPort 超出范围,则垃圾收集器将永远不会调用终结器,并且 DataPort 将永远在内存中保持活动状态。我知道发生这种情况有两个原因:

  1. 终结器中的断点永远不会被击中
  2. SOS.dll告诉我数据端口仍然存在

Sidebar:在我们进一步讨论之前,我会说是的,我知道答案是“调用 Dispose() Dummy!”但我认为即使你让所有参考资料超出范围,正确的事情也应该发生最终并且垃圾收集器应该摆脱数据端口

回到问题:使用 SOS.dll,我可以看到我的 DataPort 没有被垃圾收集的原因是因为它启动的线程仍然具有对 DataPort 对象的引用 - 通过该线程的实例方法的隐式“this”参数在跑。正在运行的工作线程不会被垃圾收集,因此正在运行的工作线程范围内的任何引用也不符合垃圾回收的条件。

线程本身基本上运行以下代码:

public void WorkerThreadMethod(object unused)
{
  ManualResetEvent dataReady = pInvoke_SubcribeToEvent(this.nativeHardwareHandle);
  for(;;)
  {
    //Wait here until we have data, or we got a signal to terminate the thread because we're being disposed
    int signalIndex = WaitHandle.WaitAny(new WaitHandle[] {this.dataReady, this.closeSignal});
    if(signalIndex == 1) //closeSignal is at index 1
    {
      //We got the close signal.  We're being disposed!
      return; //This will stop the thread
    }
    else
    {
      //Must've been the dataReady signal from the hardware and not the close signal.
      this.ProcessDataFromHardware();
      dataReady.Reset()
    }
  }
}

Dispose 方法包含以下(相关)代码:

public void Dispose()
{
  closeSignal.Set();
  workerThread.Join();
}

因为该线程是 gc 根并且它保存对 DataPort 的引用,所以 DataPort 永远不符合垃圾回收的条件。因为终结器永远不会被调用,所以我们永远不会向工作线程发送关闭信号。因为工作线程永远不会收到关闭信号,所以它会永远持续下去并保留该引用。确认!

我能想到的解决这个问题的唯一答案是去掉 WorkerThread 方法上的“this”参数(详见下面的答案)。其他人能想到另一种选择吗?一定有更好的方法来使用与对象具有相同生命周期的线程来创建对象!或者,可以在没有单独线程的情况下完成此操作吗?我选择这个特殊的设计是基于这个帖子在 msdn 论坛上,该论坛描述了常规 .NET 串行端口类的一些内部实现细节

Update来自评论的一些额外信息:

  • 有问题的线程已将 IsBackground 设置为 true
  • 上面提到的非托管资源不会影响该问题。即使示例中的所有内容都使用托管资源,我仍然会看到相同的问题

为了摆脱隐式的“This”参数,我稍微改变了工作线程方法,并将“this”引用作为参数传递:

public static void WorkerThreadMethod(object thisParameter)
{
  //Extract the things we need from the parameter passed in (the DataPort)
  //dataReady used to be 'this.dataReady' and closeSignal used to be
  //'this.closeSignal'
  ManualResetEvent dataReady = ((DataPort)thisParameter).dataReady;
  WaitHandle closeSignal = ((DataPort)thisParameter).closeSignal;

  thisParameter = null; //Forget the reference to the DataPort

  for(;;)
  {
    //Same as before, but without "this" . . .
  }
}

令人震惊的是,这并没有解决问题!

回到 SOS.dll,我看到 ThreadHelper 对象仍然保留着对我的 DataPort 的引用。显然,当你通过执行以下操作来启动工作线程时Thread.Start(this);,它创建一个私有 ThreadHelper 对象,其生命周期与保存传递给 Start 方法的引用的线程相同(我推断)。这给我们留下了同样的问题。有些东西保存着对 DataPort 的引用。让我们再试一次:

//Code that starts the thread:
  Thread.Start(new WeakReference(this))
//. . .
public static void WorkerThreadMethod(object weakThisReference)
{
  DataPort strongThisReference= (DataPort)((WeakReference)weakThisReference).Target;

  //Extract the things we need from the parameter passed in (the DataPort)
  ManualResetEvent dataReady = strongThisReferencedataReady;
  WaitHandle closeSignal = strongThisReference.closeSignal;

  strongThisReference= null; //Forget the reference to the DataPort.

  for(;;)
  {
    //Same as before, but without "this" . . .
  }
}

现在我们没事了。创建的 ThreadHelper 保留 WeakReference,这不会影响垃圾收集。我们在工作线程开始时仅从 DataPort 中提取所需的数据,然后故意丢失对 DataPort 的所有引用。在此应用程序中这是可以的,因为我们获取的部分在 DataPort 的生命周期内不会改变。现在,当顶层应用程序丢失对 DataPort 的所有引用时,它就可以进行垃圾回收。 GC 将运行终结器,该终结器将调用 Dispose 方法,该方法将杀死工作线程。一切都很幸福。

然而,这样做(或者至少是正确的)确实很痛苦!有没有更好的方法来创建一个拥有与该对象相同生命周期的线程的对象?或者,有没有办法在没有线程的情况下做到这一点?

结语:如果您可以拥有某种不需要其自己的线程但会在 Threadpool 线程上触发一次延续的等待句柄,而不是让一个线程花费大部分时间执行 WaitHandle.WaitAny(),那就太好了它被触发了。例如,如果硬件 DLL 可以在每次有新数据时调用委托,而不是发出事件信号,但我不控制该 DLL。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程阻止所有者的垃圾收集 的相关文章

  • MVC 5 中具有 ASP.NET Identity 的 Autofac 不会验证 OWIN 管道中的安全标记

    我在 MVC 5 中设置了 AutoFac 来与 ASP NET Identity 一起使用 表面上一切似乎都工作正常 即用户可以创建帐户并登录 但后来我发现 当安全标记更改时 用户不会注销 通过在 AspNetUsers 表中进行暴力破解
  • 用于在标头更改时重新编译的简单 C 项目的示例 makefile

    有谁有完整的 makefile 可以执行以下操作 如果 HEADER 文件发生更改 则重建项目 cpp 文件在 makefile 中列出 头文件未在 makefile 中列出 头文件允许与 cpp 文件具有不同的名称 部分cpp文件没有头文
  • if constexpr 中的 not-constexpr 变量 – clang 与 GCC

    struct A constexpr operator bool const return true int main auto f auto v if constexpr v A a f a clang 6 接受该代码 GCC 8 拒绝它
  • JavaScript 错误:MVC2 视图中的条件编译已关闭

    我试图在 MVC2 视图页面中单击时调用 JavaScript 函数 a href Select a JavaScript 函数 function SelectBenefit id code alert id alert code 这里 b
  • OpenGL:如何检查用户是否支持glGenBuffers()?

    我检查了文档 它说 OpenGL 版本必须至少为 1 5 才能制作glGenBuffers 工作 用户使用的是1 5版本但是函数调用会导致崩溃 这是文档中的错误 还是用户的驱动程序问题 我正在用这个glGenBuffers 对于VBO 我如
  • C# 获取数据表中所有重复行的计数

    我通过运行存储过程来填充数据集 并且从数据集中填充数据表 DataSet RawDataSet DataAccessHelper RunProcedure storedprocedureName this will just return
  • 如何防止 Blazor NavLink 组件的默认导航

    从 Blazor 3 1 Preview 2 开始 应该可以防止默认导航行为 https devblogs microsoft com aspnet asp net core updates in net core 3 1 preview
  • 在 azure blob 存储中就地创建 zip 文件

    我将文件存储在 Blob 存储帐户内的一个容器中 我需要在第二个容器中创建一个 zip 文件 其中包含第一个容器中的文件 我有一个使用辅助角色和 DotNetZip 工作的解决方案 但由于 zip 文件的大小最终可能达到 1GB 我担心在进
  • Unity c# 四元数:将 y 轴与 z 轴交换

    我需要旋转一个对象以相对于现实世界进行精确旋转 因此调用Input gyro attitude返回表示设备位置的四元数 另一方面 这迫使我根据这个四元数作为默认旋转来计算每个旋转 将某些对象设置为朝上的简单方法如下 Vector3 up I
  • 让网络摄像头在 OpenCV 中工作

    我正在尝试让我的网络摄像头在 Windows 7 64 位中的 OpenCV 版本 2 2 中捕获视频 但是 我遇到了一些困难 OpenCV 附带的示例二进制文件都无法检测到我的网络摄像头 最近我发现这篇文章表明答案在于重新编译一个文件 o
  • 如何在多线程应用程序中安全地填充数据并 Refresh() DataGridView?

    我的应用程序有一个 DataGridView 对象和一个 MousePos 类型的列表 MousePos 是一个自定义类 它保存鼠标 X Y 坐标 类型为 Point 和该位置的运行计数 我有一个线程 System Timers Timer
  • MySQL 连接器 C++ 64 位在 Visual Studio 2012 中从源代码构建

    我正在尝试建立mySQL 连接器 C 从源头在视觉工作室2012为了64 bit建筑学 我知道这取决于一些boost头文件和C 连接器 跑步CMake生成一个项目文件 但该项目文件无法编译 因为有一大堆非常令人困惑的错误 这些错误可能与包含
  • SQLAPI++ 的免费替代品? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 是否有任何免费 也许是开源 的替代品SQLAPI http www sqlapi com 这个库看起来
  • 使用 gcc 时在头文件中查找定义的好方法是什么?

    在使用 gcc 时 有人有推荐的方法在头文件中查找定义吗 使用 MSVC 时 我只需右键单击并选择 转到定义 这非常好 我使用过 netbeans gcc 它确实有代码帮助 包括到定义的超链接 所以这是一种选择 但是 我想知道是否有任何其他
  • C++ 指针引用混淆

    struct leaf int data leaf l leaf r struct leaf p void tree findparent int n int found leaf parent 这是 BST 的一段代码 我想问一下 为什么
  • 每个客户端一个线程与线程服务器的排队线程模型之间的相对优点?

    假设我们正在构建一个线程服务器 旨在在具有四个核心的系统上运行 我能想到的两种线程管理方案是每个客户端连接一个线程和一个排队系统 正如第一个系统的名称所暗示的那样 我们将为每个连接到服务器的客户端生成一个线程 假设一个线程始终专用于程序的主
  • 如何从 Windows Phone 7 模拟器获取数据

    我有一个 WP7 的单元测试框架 它在手机上运行 结果相当难以阅读 因此我将它们写入 XDocument 我的问题是 如何才能将这个 XML 文件从手机上移到我的桌面上 以便我可以实际分析结果 到目前为止 我所做的是将 Debugger B
  • 如何组合两个 lambda [重复]

    这个问题在这里已经有答案了 可能的重复 在 C 中组合两个 lambda 表达式 https stackoverflow com questions 1717444 combining two lamba expressions in c
  • winform c# 中的弹出窗口

    我正在开发一个需要弹出窗口的项目 但问题是我还希望能够通过表单设计器在此弹出窗口中添加文本框等 所以基本上我有一个按钮 当您单击它时 它将打开我在表单设计器中设计的另一个窗口 我一直在谷歌搜索 但还没有找到我需要的东西 所以我希望你们能帮助
  • ContentDialog Windows 10 Mobile XAML - 全屏 - 填充

    我在项目中放置了一个 ContentDialog 用于 Windows 10 上的登录弹出窗口 当我在移动设备上运行此项目时 ContentDialog 未全屏显示 并且该元素周围有最小的填充 在键盘上可见 例如在焦点元素文本框上 键盘和内

随机推荐

  • 如何使用正则表达式用方法结果替换匹配组项

    输入字符串是这样的 A线 50 线路N 120 A线 12 B线 53 我想用以下结果替换 LineB 值MultiplyCalculatorMethod LineAValue where LineAValue是上面一行的值LineB an
  • 将 HTML 输入数字上的向下箭头变得更大、更清晰

    而不是输入 数字 时是否可以始终显示向上 向下箭头 我希望能够使向上 向下箭头更大更清晰 What I have right now I need to make them bigger like this 您可以将输入包装在元素中并设置其
  • 错误:请提供起始值

    我正在 R 中进行对数二项式回归 我想控制模型中的协变量 年龄和 BMI 都是连续变量 而因变量是结果 是或否 自变量是组 1 或 2 fit lt glm Outcome Group data data 1 family binomial
  • 如何将焦点设置为 JScrollPane 内 JTable 中的第一行

    我有一个JTable里面一个JScrollPane 我已将其放入面板中 面板加载后 我希望 JTable 中的第一行获得焦点 但默认情况下焦点会转到JScrollPane按下 Tab 键 焦点进入表格第一行 我不想使用setRowSelec
  • ThreeJS 阴影未渲染

    我浏览过其他一些 S O 问题 遵循了所有建议 但我仍然不知道为什么我无法在这个非常基本的场景上渲染阴影 http jsfiddle net 4Txgp 更新 代码 var SCREEN WIDTH window innerWidth 25
  • 下载闪亮的 rpivotTable 输出

    我发现了一个有趣的包rpivotTable 我想创建shiny app包括rpivotTable可以使用下载生成的数据downloadHandler 但是 我无法找到解决方案 如何创建data frame或者我可以传递给的其他东西downl
  • 如何使这个 eav 查询产生水平结果

    案子 tables product product id name 1 iphone 4 2 gallaxy 2 3 blackbery 6 product attribute id product id attribute id 1 1
  • 如何使用 JAXB 解组重复的嵌套类?

    我如何指示 JAXB 处理这个问题 XML
  • Python向dbf写入数据时出错

    我得到这个错误 DbfError unable to modify fields individually except in with or Process 如何修复它 这是我的code with dbf Table aa dbf as
  • 从网页向 chrome 扩展程序发送消息

    我想从随机网页的控制台发送消息到我的 chrome 扩展程序 chrome extension sendMessage 似乎不起作用 根据官方文档你应该使用postMessage在发送者和message接收器中的事件监听器 这是一个例子 您
  • 如何通过 Runge-Kutta 4 传递硬编码微分方程

    我正在尝试实施 Runge Kutta 来解决示例问题 C 中的 dy dt y t 2 1 和 dy dt t y t 3 我似乎无法获得我期望的输出 我已将我的程序分成几个类 以尝试单独查看工作 我认为我的主要错误来自尝试使用委托将方法
  • Google 应用程序引擎 Node.js TLS 1.2

    我们的应用程序托管在 Google App Engine Node js 灵活环境 上 我们目前正在接受安全检查 但未能解决 Google App Engine 支持 TLS 1 0 和 1 1 版本的问题 有没有办法强制只使用 TLS 1
  • 在react router中显示路由之间的简单加载指示器

    我来自AngularJS世界并在几天前开始编写我的第一个 React 应用程序react router in AngularJS I do app directive Loading function rootScope timeout r
  • 如何从字符串中获取浮点值

    我有一个像这样的字符串 gt 12 4N m kg 我需要从上面的字符串中获取一个值12 4 当我使用替换所有功能时str replaceAll 0 9 当字符串有两个点时 这不起作用 浮点值的位置可能不同 首先丢弃所有非浮点字符 然后转换
  • 由于@font-face,jQuery 计算出了错误的高度

    我对 jQuery 和 font face 有疑问 我需要计算出 a 的高度 div 效果很好 但是字体加载时会有一点延迟 一旦加载 font face 字体实际上就比后备字体大 因此高度比应有的要小 我尝试过使用 Modernizr 但这
  • 如何在 JavaConfig 中定义 http“security = 'none'?

    我想使用 Java Config 在 Spring Boot 中定义类似于此 XML 的内容
  • 水晶报告图像被压扁

    Crystal Reports v11 如果这很重要 中是否有任何方法可以防止图像自行拉伸以适合整个 OLE 对象 我正在从数据库动态加载图像 但不知道它们的长宽比 感谢帮助 我找到了答案here 要使图像正确调整大小 必须按顺序执行以下步
  • Elasticsearch 超时 true 但仍然得到结果

    我将搜索查询的超时设置为 10 毫秒 因此我预计 elasticsearch 搜索查询应在 10 毫秒内超时 在回应中 我确实得到了 timed out true但查询似乎没有超时 它仍然运行几百毫秒 响应示例 took 460 timed
  • 如何为每个请求执行通用代码?

    有没有可能找到类似的功能Page Load 我有 MVC 应用程序 我需要在每个页面加载或重新加载时运行一些代码 或者调用一些控制器 所有类都有一个共享函数 我尝试 Application Start 但这仅在应用程序第一次运行时执行 我搜
  • 线程阻止所有者的垃圾收集

    在我创建的库中 我有一个 DataPort 类 它实现与 NET SerialPort 类类似的功能 它与某些硬件进行通信 并且每当数据通过该硬件传入时就会引发一个事件 为了实现此行为 DataPort 启动一个线程 该线程预计具有与 Da