【问题标题】:Synchronising threads between C++/CLI and C#在 C++/CLI 和 C# 之间同步线程
【发布时间】:2016-05-15 17:57:32
【问题描述】:

假设我有以下C++/CLI 类:

public ref class ManagedDLAContainer {

private:
    DLAContainer* native_dla_container;
public:
    ManagedDLAContainer() : native_dla_container(new DLAContainer()) {}
    ~ManagedDLAContainer() { delete native_dla_container; }

    KeyValuePair<int,int> GetMRAParticle() {
        std::pair<int,int> mra_p = native_dla_container->mra_particle();
        KeyValuePair<int,int>^ mra_kvp = gcnew 
                     KeyValuePair<int,int>(mra_p.first, mra_p.second);
        return *mra_kvp;
    }

    size_t Size() {
        return native_dla_container->size();
    }

    void Generate(size_t _n) {
        native_dla_container->generate(_n);
    }

};

其中DLAContainer 是一个非托管的本机C++ 类。此类的方法generate 执行涉及建立粒子系统的计算密集型计算,而mra_particle 将代表最近添加粒子的std::pair&lt;int,int&gt; 返回到DLAContainer。这个C++/CLI 代码被打包在一个类库中,然后由C# WPF 项目使用。

WPF 项目有以下类:

public partial class MainWindow : Window {
     private static readonly object locker = new object();
     private readonly ManagedDLAContainer dla;
     private KeyValuePair<int,int> mra_pair;
     private readonly AggregateSystemManager aggregate_manager;

     public MainWindow() {
         InitializeComponent();
         dla = new ManagedDLAContainer();
         mra_pair = new KeyValuePair<int,int>();
         aggregate_manager = new AggregateSystemManager();
         // a Model3DGroup which is part of the GUI
         WorldModels.Children.Add(aggregate_manager.AggregateSystemModel());
     }

     private void AggregateUpdateListener(uint _particle_slider_val){
         while (dla_2d.Size() < _particle_slider_val) {
             KeyValuePair<int,int> agg_kvp = dla.GetMRAParticle();
             if (agg_kvp.Equals(mra_pair) {
                 // no updates to aggregate
             }
             else {
                 mra_pair = agg_kvp;
                 Point3D position = new Point3D(agg_kvp.Key, agg_kvp.Value,0);
                 aggregate_manager.AddParticle(position);
                 Dispatcher.Invoke(() => { aggregate_manager.Update(); } );
             }
         }
     }

     private void GenerateAggregate() {
         lock(locker) {
             uint particle_slider_val = 0;
             Dispatcher.Invoke(() => {
                 particle_slider_val = (uint)particles_slider.Value;
             });
             // start AggregateUpdateListener in new task
             Task.Factory.StartNew(() => AggregateUpdateListener(particle_slider_val));
             // generate the aggregate
             dla.Generate(particle_slider_val);
         }
     }

     private void GenerateButtonHandler(object sender, RoutedEventArgs e) {
         // start GenerateAggregate method in new task
         Task.Factory.StartNew(() => GenerateAggregate());
     }

}

程序流程说明

  • 用户使用particle_slider GUI 元素设置要生成的粒子数,然后单击生成按钮。
  • 方法GenerateAggregate在使用Task.Factory.StartNew的新任务中运行,该函数然后在单独的任务中运行AggregateUpdateListener,最后调用Generate生成粒子系统。
  • AggregateUpdateListenerGenerate 运行的同时持续运行,并检查最近添加的粒子的更新,并根据需要使用AggregateManager 类将新粒子渲染到界面。

问题

虽然这个程序大部分是成功的,但偶尔使用ManagedDLAContainer::Generate(size_t) 生成的粒子会被AggregateUpdateListener 方法遗漏,从而导致界面中显示的粒子系统出现间隙。

我认为这里的问题是这两个过程(粒子系统的生成和检查-渲染过程)没有以正确同步的方式运行。我需要以某种方式获取它,以便在将粒子添加到系统时触发一个事件,该事件允许AggrgegateUpdateListener 然后执行渲染,然后将控制权交还给生成。

但是我不确定如何执行此操作,因为我的Generate 函数将在后台不间断运行,直到粒子系统完全生成到所需的粒子数量 - 这个过程通过本机 C++ 幕后代码,对我的 C# 项目一无所知。正是出于这个原因,我认为在这种情况下使用 AutoResetEvent 之类的东西不适用,但如果是这样,请告诉我如何操作!

目前我能想到的唯一解决方案(与正确同步进程无关)是遍历 GUI 的最终粒子系统并与 @987654347 的粒子系统容器进行比较检查@ 代码(这将永远是正确的)并在与后者的比较中检测到未命中时填补前者中的任何缺失空白。但这是一个令人讨厌的“解决方案”,我宁愿让它实时正确运行。

如果需要任何进一步的信息,请告诉我。

【问题讨论】:

  • 那么,为什么不直接使用.NET部分的Monitor类来同步呢?
  • 也许是因为C++部分没有被管理...?
  • 根本看不到同步。我们必须相信“最近添加”的意思是它所说的。您只是无法保证 最近 是您真正喜欢的。每当 UI 线程忙碌或垃圾收集时,AggregateUpdateListener() 方法就会陷入 Invoke() 调用,这完全是随机的。所以很容易错过两个添加的粒子,它只看到最近添加的粒子。似乎缺少的是可以存储添加的粒子的线程安全队列。
  • DLAContainer 是否有一种方法可以让您检索每个处理过的粒子?
  • @Slugart 目前没有,但添加起来很简单

标签: c# multithreading synchronization c++-cli


【解决方案1】:

您可以尝试在 C++ 和 C# 中使用命名信号量,但它可能有点重,因为它用于进程之间的同步。

否则,按照 Hans 的评论,您可以在托管 C++ 部分中创建 BlockingCollection 并将其公开给 C# 项目。然后,您需要消耗 ManagedDLAContainer 中的所有粒子并将它们排入阻塞队列。

在 C# GUI 中,我建议您每 200/250 毫秒设置一个计时器,当它触发时,队列中的所有可用粒子都会出列,然后更新 GUI。确保通过一些最大更新数来限制它,这样您就不会被卡住不断地从队列中拉出项目(如果本机代码比 C# 代码快)。

【讨论】:

  • 这些是很好的建议,我会看看BlockingCollection 看看我是否可以让它发挥作用。
  • 我现在会接受这个答案,我已经在 C++/CLI 中实现了 BlockingCollection 以及在我的 c++ 代码中实现了 std::queue - 似乎运行良好,现在唯一的问题是我在清除并再次运行模拟时得到deque iterator not dereferencable,但这是我应该能够弄清楚的。
猜你喜欢
  • 2017-12-08
  • 2011-01-12
  • 2011-06-15
  • 1970-01-01
  • 1970-01-01
  • 2016-07-08
  • 1970-01-01
  • 1970-01-01
  • 2014-11-02
相关资源
最近更新 更多