不同的线程访问 MemoryStream

Posted

技术标签:

【中文标题】不同的线程访问 MemoryStream【英文标题】:different thread accessing MemoryStream 【发布时间】:2010-05-13 07:52:30 【问题描述】:

有一些代码通过调用 GetBuffer() 将数据直接写入 MemoryStream 对象的数据缓冲区。它还适当地使用和更新 Position 和 SetLength() 属性。

此代码在 99.9999% 的情况下都能正常工作。字面上地。只有每这么多 100,000 次迭代它才会呕吐。具体问题是 MemoryStream 的 Position 属性突然返回零而不是适当的值。

但是,添加了检查 0 并引发异常的代码,该异常在单独的方法中包含 MemoryStream 属性(如 Position 和 Length)的日志。那些返回正确的值。在同一方法中进一步添加日志记录表明,当这种罕见情况发生时,该特定方法内的 Position 仅为零。

好的。显然,这一定是线程问题。而且很可能是编译器优化问题。

但是,该软件的本质是它是由带有调度程序的“任务”组织的,因此几个实际的 O/S 线程中的任何一个都可以在任何给定时间运行此代码 - 但一次不能超过一个.

所以我的猜测是,通常情况下,同一个线程一直被用于此方法,然后在极少数情况下使用不同的线程。 (只需编写代码,通过捕获和比较线程 id 来测试这个理论。)

然后由于编译器优化,不同的线程永远不会得到正确的值。它得到一个“陈旧”的值。

通常在这种情况下,我会将“volatile”关键字应用于相关变量,以查看是否可以修复它。但在这种情况下,变量位于 MemoryStream 对象内。

有人有其他想法吗?或者这是否意味着我们必须实现自己的 MemoryStream 对象?

真诚地, 韦恩

编辑:刚刚运行了一个测试,该测试计算对该方法的调用总数,并计算 ManagedThreadId 与上次调用不同的次数。它几乎恰好有 50% 的时间切换线程——在它们之间交替。所以我上面的理论几乎肯定是错误的,否则错误会更频繁地发生。

编辑:这个错误发生得非常少,以至于在没有错误的情况下需要将近一周的时间才能感觉到它真的消失了。相反,最好进行实验以准确确认问题的性质。

编辑:当前锁定是通过使用 MemoryStream 的 5 种方法中的每个方法中的 lock() 语句处理的。

【问题讨论】:

同步操作调用所有需要的内存屏障,以确保用户空间变量的语义正确。你不应该需要volatile。还有其他问题。 【参考方案1】:

(确实需要示例代码来确认这一点。)

MemoryStream 成员未记录为线程安全(例如Position),因此您需要确保您只能从一个线程访问此实例(或任何对逻辑上是MemoryStream 一部分的对象的引用)一次。

但是MemoryStream没有被记录为具有线程亲和性,因此您可以从不同的线程访问实例——只要这种访问不是并发的。

线程很难(本问答中的公理)。

我建议您进行一些并发访问,两个线程同时访问同一个实例,这有时会破坏实例状态的某些方面。

我会确保我使锁定尽可能简单(尝试更加聪明并限制锁定通常是很难找到错误的原因)并让事情正常工作。在多核系统上进行测试也可能有所帮助。仅当分析表明存在显着净(整个应用程序)收益的潜力时,才尝试优化锁定。

【讨论】:

非常感谢您的帮助!请参阅我上面的编辑。另外,我目前有一个实验,它使用“countThreads”变量的 interlocked.increment 和递减,如果 countThreads > 1 则抛出异常。这个逻辑包装了名为 Data 的属性,这是其他对象访问 MemoryStream 的方式。这永远不会被绊倒,这就是为什么似乎只涉及一个线程的原因。但是,类中的方法仅直接使用“数据”类字段。我正在修改它们以也使用 Data 属性来查看是否有 2 个线程同时访问它。 以上评论证明你是对的。正在从两个不同的线程访问 Data 属性。我建议使用这种技术进行发现。实际上编写代码以捕获 2 个线程调用并从每个线程获取堆栈跟踪。这就是我所做的。然后结果证明是由于启动了一个单独的线程以清除 tcp/ip 套接字中的剩余字节。这就是为什么这种情况如此罕见。它只有在套接字关闭过程中才有可能发生。 对不起。以为解决了,结果又复发了。这个问题只是问题的一部分。 MemoryStream 有时仍会获得零位置,即使任何线程冲突并非不可能。我已经覆盖了所有方法并添加了一个 volatile 位置和长度变量,这是唯一剩下的问题,因为由于 MSDN 文档说 MemoryStream 不是线程安全的,它们可能意味着由于编译时的编译器优化,它也具有一些线程亲和力在发布模式。如果问题得到解决,我会在几天后发布。 最终,正如您所描述的,这绝对是一个线程问题。所以你得到了正确答案的功劳。

以上是关于不同的线程访问 MemoryStream的主要内容,如果未能解决你的问题,请参考以下文章

访问从不同线程访问的控件时如何处理无效的跨线程操作?

调用线程无法访问此对象,因为不同的线程拥有它

调用线程无法访问此对象,因为不同的线程拥有它

“调用线程无法访问此对象,因为不同的线程拥有它”从 WPF 中的不同线程更新 UI 控件时出现错误

调用线程无法访问此对象,因为不同的线程拥有它 - WPF [重复]

不同的线程访问 MemoryStream