【问题标题】:C# Raise events on another threadC# 在另一个线程上引发事件
【发布时间】:2015-04-24 18:40:34
【问题描述】:

我有MainForm 类和一些Engine 类用于相机控制。在Engine 类中,我有一些Camera 对象,它是来自相机生产商的SDK 的一部分。 这个Camera 有可用的OnNewFrame 事件,所以我将初始化:

camera.OnNewFrame += frameAcquired;

frameAcquired 也是 Engine 类的成员。

 private void frameAcquired(object sender, EventArgs e)
 {
    /* this event should periodically raise after ~17ms,
       but sometimes it hangs for a short time 
       (when I overloads main thread) */
 }

Engine 对象是MainForm 类的成员。在这里,我正在显示来自相机的图像并做一些其他的图形工作。问题是MainForm 线程有时会挂起很短的时间。它对于显示不是那么重要,但它对于 camera.OnNewFrame 事件(我正在使用 60 fps),因为这也由于主线程延迟而延迟。

是否有可能以某种方式确保Engine 对象(或Engine 中的Camera 对象)会从它自己的线程而不是主线程引发事件?换句话说,确保此事件以 SDK 生产者设置的速率提高,而不依赖于我的主线程。

【问题讨论】:

  • 很不清楚为什么会出现这个问题,如果这导致相机固件因某种原因爆炸,您将不得不复制图像。最终有一个基本的firehose问题,当相机吐出图像的速度超过UI线程可以显示它们的速度时,如果你不使用任何类型的视频加速,你肯定会以60 fps的速度突破极限,那么你会输不定期更新。
  • 显示没问题。我正在使用一些框架队列,如果旧框架尚未显示,我将丢弃新框架的显示。但我需要将它们全部保存(在另一个线程上执行此操作没有问题)。但有时当主线程挂起时(例如调整表单大小 -> 我的代码中的一些计算,以及其他情况),事件引发也会挂起。
  • “事件引发”只有在您错误地使用 Invoke() 而不是 BeginInvoke() 时才会挂起。
  • 您可以使用BeginInvoke 代替Invoke(wpf 中的InvokeAsync),这不会阻止您的事件处理。或者您可以在单独的线程中执行事件处理程序(因此,如果一个被阻塞,其他人仍在处理),这必须包括同步。但当然正确的方法是实现某种消费者-生产者队列,屏幕占据最后一帧。
  • 我没有在这里使用Invoke 也不是BeginInvoke。我创建Engine 对象,并在Engine 类中初始化事件。你能建议我如何使用 BeginInvoke 吗?不幸的是,我不知道该事件是如何在 SDK 内部引发的。

标签: c# multithreading events main


【解决方案1】:

不久前我遇到了类似的问题。我已经在 C++/CLI 中处理过它,所以同样的方法也应该在 C# 中起作用。

我相信您已经在 MainForm 中初始化了 Engine 类。如果你想从另一个线程引发事件,那么这个对象必须在另一个线程中初始化。

我相信你应该尝试在 MainForm 构造函数中创建一个新的Thread

MyForm()
{
    //rest of your constructor
    cameraThread = new Thread(new ParameterizedThreadStart(CameraRun));
    cameraThread.Name = "Camera Thread";
    cameraThread.Start(this);
    while (!cameraThread.IsAlive)
        Thread::Sleep(1);
}

这样您就可以在 MyForm 类中为 cameraThread 保留一个字段。不过,您需要编写一个函数来运行新线程。我们知道它将初始化您的 Engine 类,但这还不是全部。只是为了确保线程没有完成你给它运行的函数,在线程函数的底部添加一些检查,如下所示:

void CameraRun(Object myForm)
{
    //you can use (myForm as MyForm)
    //and make calls from here
    /*
    Engine initialization etc
    */
    while((myForm as MyForm).isShown)
        Sleep(100);
}

cameraThread 应该加入 MainForm 析构函数中的主代码。

~MyForm()
{
    //rest of your destructor
    this.isShown=false;
    cameraThread.Join();
}

如果您愿意,可以将 this.isShown=false 加入 OnFormClosed() 事件。

如果你能走到这一步,那就太好了。但是,不幸的是,您还没有完成。由于您现在正在处理多个线程,因此您必须确保以线程安全的方式访问对象。长话短说,请查看answer

编辑:一些更正

【讨论】:

  • 我知道您来自 C++,但 C# 的“解构器”与 C++ 不同。你不应该在~MyForm() 方法中做类似this.isShown=false; 的事情。此外while (!cameraThread->IsAlive) Thread.Sleep(1); 将锁定 C# 中的 UI。
  • 我不明白为什么不呢?介意解释吗?编辑:它会,直到线程还活着。
  • isShown 只是一个布尔字段,用于跟踪表单是否仍在显示中。
  • 好的,你可以设置一个布尔值,但~ 不会在 C# 中执行“析构函数”,它会创建一个 Finalizer,它的行为与析构函数非常不同.最重要的是它不是有序的或确定性的,您可以在有机会完成之前完成您拥有的成员,这可能会导致您尝试访问不再存在的对象。
  • C# 中的 class classname { ~classname() {} } 与 C++ 中的 class classname { !classname() {} }; 相同。请参阅 MSDN 上的 Destructors and Finalizers in Visual C++
猜你喜欢
  • 1970-01-01
  • 2012-11-21
  • 1970-01-01
  • 2013-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多