在 C++/CLI 和 C# 之间同步线程
Synchronising threads between C++/CLI and C#
假设我有以下 C++/CLI
class:
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++
class。此 class 的方法 generate
进行涉及建立粒子系统的计算密集型计算,而 mra_particle
returns 一个 std::pair<int,int>
代表 最近添加的 个粒子到 DLAContainer
。此 C++/CLI
代码打包在 class 库中,然后由 C#
WPF 项目使用。
WPF 项目有以下 class:
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
来生成粒子系统。
AggregateUpdateListener
连续 运行s 而 Generate
正在 运行ning 并检查 最近添加的粒子 [=64] 的更新=] 并根据需要使用 AggregateManager
class 将新粒子渲染到界面。
问题
虽然这个程序大部分是成功的,但偶尔使用 ManagedDLAContainer::Generate(size_t)
生成的粒子会被 AggregateUpdateListener
方法遗漏,导致界面中显示的粒子系统出现间隙。
我认为,这里的问题是这两个过程(粒子系统的生成和检查渲染过程)没有运行以正确同步的方式进行。我需要以某种方式获取它,以便在将粒子添加到系统时触发一个事件,允许 AggrgegateUpdateListener
然后执行渲染,然后将控制权交还给生成。
但是我不确定如何执行此操作,因为我的 Generate
函数将 运行 在后台不停地运行,直到粒子系统完全生成达到所需的粒子数- 这个过程是通过幕后的本机 C++
代码执行的,它对我的 C#
项目一无所知。正是出于这个原因,我认为在这种情况下使用 AutoResetEvent
之类的东西不适用, 但如果适用,请告诉我如何使用!
目前我能想到的唯一解决方案(与正确同步进程无关)是迭代 GUI 的最终粒子系统,并与 GUI 的粒子系统容器进行比较检查C++
代码(永远是正确的)并在与后者的比较中检测到未命中时填补前者中的任何缺失空白。但这是一个令人讨厌的 "solution",我宁愿实时正确 运行ning。
如果需要任何进一步的信息,请告诉我。
您可以尝试在 C++ 和 C# 中使用命名信号量,但是它可能有点重,因为它用于进程之间的同步。
否则,按照 Hans 的评论,您可以在托管 C++ 部分中创建一个 BlockingCollection 并将其公开给 C# 项目。然后,您需要消耗 ManagedDLAContainer 中的所有粒子并将它们排入阻塞队列。
在 C# GUI 中,我建议您每 200/250 毫秒有一个计时器,当它触发时会使队列中的所有可用粒子出列,然后更新 GUI。确保通过一些最大更新数量来限制它,这样你就不会卡在不断地从队列中拉出项目(如果本机代码比 C# 代码快)。
假设我有以下 C++/CLI
class:
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++
class。此 class 的方法 generate
进行涉及建立粒子系统的计算密集型计算,而 mra_particle
returns 一个 std::pair<int,int>
代表 最近添加的 个粒子到 DLAContainer
。此 C++/CLI
代码打包在 class 库中,然后由 C#
WPF 项目使用。
WPF 项目有以下 class:
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
来生成粒子系统。 AggregateUpdateListener
连续 运行s 而Generate
正在 运行ning 并检查 最近添加的粒子 [=64] 的更新=] 并根据需要使用AggregateManager
class 将新粒子渲染到界面。
问题
虽然这个程序大部分是成功的,但偶尔使用 ManagedDLAContainer::Generate(size_t)
生成的粒子会被 AggregateUpdateListener
方法遗漏,导致界面中显示的粒子系统出现间隙。
我认为,这里的问题是这两个过程(粒子系统的生成和检查渲染过程)没有运行以正确同步的方式进行。我需要以某种方式获取它,以便在将粒子添加到系统时触发一个事件,允许 AggrgegateUpdateListener
然后执行渲染,然后将控制权交还给生成。
但是我不确定如何执行此操作,因为我的 Generate
函数将 运行 在后台不停地运行,直到粒子系统完全生成达到所需的粒子数- 这个过程是通过幕后的本机 C++
代码执行的,它对我的 C#
项目一无所知。正是出于这个原因,我认为在这种情况下使用 AutoResetEvent
之类的东西不适用, 但如果适用,请告诉我如何使用!
目前我能想到的唯一解决方案(与正确同步进程无关)是迭代 GUI 的最终粒子系统,并与 GUI 的粒子系统容器进行比较检查C++
代码(永远是正确的)并在与后者的比较中检测到未命中时填补前者中的任何缺失空白。但这是一个令人讨厌的 "solution",我宁愿实时正确 运行ning。
如果需要任何进一步的信息,请告诉我。
您可以尝试在 C++ 和 C# 中使用命名信号量,但是它可能有点重,因为它用于进程之间的同步。
否则,按照 Hans 的评论,您可以在托管 C++ 部分中创建一个 BlockingCollection 并将其公开给 C# 项目。然后,您需要消耗 ManagedDLAContainer 中的所有粒子并将它们排入阻塞队列。
在 C# GUI 中,我建议您每 200/250 毫秒有一个计时器,当它触发时会使队列中的所有可用粒子出列,然后更新 GUI。确保通过一些最大更新数量来限制它,这样你就不会卡在不断地从队列中拉出项目(如果本机代码比 C# 代码快)。