用于 C++ 对象销毁的 C# 一次性模式

标签 c# c++ .net multithreading disposable

我遇到了微软提供的这种一次性模式的实现:https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx

using System;

class BaseClass : IDisposable
{
   // Flag: Has Dispose already been called?
   bool disposed = false;

   // Public implementation of Dispose pattern callable by consumers.
   public void Dispose()
   { 
      Dispose(true);
      GC.SuppressFinalize(this);           
   }

   // Protected implementation of Dispose pattern.
   protected virtual void Dispose(bool disposing)
   {
      if (disposed)
         return; 

      if (disposing) {
         // Free any other managed objects here.
         //
      }

      // Free any unmanaged objects here.
      //
      disposed = true;
   }

   ~BaseClass()
   {
      Dispose(false);
   }
}

假设我有一个与此 C# 类相关联的 C++ 类,我想在处置 C# 类时删除 C++ 对象,以确保正确释放我的非托管资源。我添加了一个函数 DestructNative(self),它基本上对关联的 C++ 对象进行原生 C++ 调用 delete (CppObject*)self。所以我的代码看起来像这样:

   // Protected implementation of Dispose pattern.
   protected virtual void Dispose(bool disposing)
   {
      if (disposed)
         return; 


      if (disposing) {
         // Free any other managed objects here.
         //
      }

      DestructNative(self);
      disposed = true;
   }

所以我的问题是,知道 C# 终结器可以从不同的线程调用,我是否需要在 C++ 对象的析构函数中提供同步以确保在 Dispose(false)< 时我没有任何竞争条件 从 C# 终结器调用?

附加问题

微软一次性模式坏了吗?看起来 disposed 标志是一个简单的变量,如果从不同的线程调用终结器,它是不同步的。

最佳答案

Is microsoft disposable pattern broken? Seems like disposed flag is a simple variable which is not synchronized if finalizer is called from a different thread.

不,它没有坏。

这个问题提出了 2 个有趣的问题。对于早于 C++11 思想且线程不感知的类,以下影响是什么。

 class PreCpp11 {
   public:
     int ** ptr;
     bool   mInitDone;
     PreCpp11() : mInitDone(false) {
         ptr = new int*[100];
     }
     init() {
         for( size_t i = 0; i < 100; i++ ){
             ptr[i] = new int[100];
         }
         mInitDone = true;
     }
     ~PreCpp11() {
        if( mInitDone ){
            for( size_t i =0; i <100; i++ ){
                delete ptr[i];
            }
        }
        delete []ptr;
     }
 }

代码之后

PreCpp11 * myObj = new PreCpp11();
myObj->init();
send_object_to_thread2( myObj );

线程2执行的地方

 PreCpp11 obj = get_obj_from_sync();
 delete obj;

如果在不同的线程上调用析构函数,我们如何避免数据竞争。

考虑到一次性实现是否会导致上述数据竞争。

在这两种情况下,我都认为此代码的答案是可以接受且合规的。但是,它依赖于 PreCpp11 对象的线程间通信以使其自身符合标准。

我的想法....

我有一大堆数据竞争机会,这个线程可以保证看到我写入 ptr 数组的值,但其他线程不能保证 inter-thread happens-before发生关系。

但是,当我在线程间与第二个线程通信我的类时,发生同步以确保我的指针在启动线程和“处理”线程之间正确同步,创建一个线程间发生-before 关系,鉴于此发生在我调用 init 之后,线程 2 中看到的值是线程 1 看到的值,当它开始与第二个线程通信时。

因此,如果线程 1 在将对象提供给线程 2 后继续修改该对象,则可能会发生数据竞争,但假设线程之间的通信是兼容的,那么第二个线程会看到第一个行为。

来自 cppreference memory_order

前序

 ->init() is sequenced before 
 send_object_to_thread2( myObj );

发生在

之前
->init() happens before the synchronized communication with thread2.

线程间先发生

->init() happens before thread 2 gets the data and calls the destructor
 ->init() is sequenced-before the synchronized write to the inter-thread communication, and the write occurs before the synchronized read.
 The actual write-read is ordered, as they are synchronized.

因此只要对象的线程间通信是同步的,并且在移交到新线程后不会发生对象的进一步修改,就不存在数据竞争。

关于用于 C++ 对象销毁的 C# 一次性模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45850376/

相关文章:

C# 类与其他 .NET 语言互操作

.net - 确定 HttpWebRequest 发出的总请求,包括重定向

.net - Dim v As String() 和 Dim v() As String 有什么区别?

c# - 覆盖标题栏按钮的工具提示文本(关闭、最大化、最小化、帮助)

c# - 在 C# 应用程序中打开文本文件

c# - 何时使用 Request.Cookies 而不是 Response.Cookies?

c++ - 转换为递归函数?

c++ - 在 QTreeView 中异步加载数据

c++ - 未对 C++ STL 容器对象调用clear() 时发生内存泄漏

c# - 无法拆箱为 Int