从次要线程更新控件:Invoke的性能是否关键?

4
我正在开发的C#应用程序主要涉及对来自相机的图像进行操作并将其打印在PictureBox上。它有一个用C++编写的库(imgcam),可以从网络摄像头中检索图像并复制它们(imgcam_copy),然后将它们传递给请求它们的托管代码。反过来,应用程序的托管部分由执行while循环的辅助线程组成,该循环从非托管库中获取图像并将其打印到PictureBox上。
一切都运行良好,最棘手的部分是从非托管到托管代码的动态资源管理,这方面我清楚地知道自己在做什么(至少我希望如此),但问题在于编写使线程正常工作所需的代码。实际上,在浏览了几个小时的网页之后,有许多事情对我来说还不清楚。
我被迫使用UI线程打印图像,因此必须依赖Invoke。
    delegate void setImageCallback(Image img);
    private void showFrame(Image img)
    {
        if (pboxCam.InvokeRequired)
        {
            this.Invoke(new setImageCallback(showFrame), img);
        }
        else
        {
            pboxCam.Image = img;
        }
    }

我有很多问题:

1)Invoke是一个昂贵的操作吗? 我尽力减少对图像主要操作的执行时间,但是在显示结果时浪费部分收益会让人失望。

2)您认为是使用同步Invoke还是异步BeginInvoke + 图像复制更好?

3)将图片作为函数参数而不是访问类成员可能有帮助吗?

    private delegate void setImageCallback();
    private void showFrame()
    {
        if (pboxCam.InvokeRequired)
        {
            this.Invoke(new setImageCallback(showFrame));
        }
        else
        {
            pboxCam.Image = cm.bitmap;
        }
    }

4)也许你不会担心性能问题,但我想知道你是否关心线程安全问题。UI线程只想显示一张图片,而非UI线程则在复制它,依赖于异步机制真的不安全吗?

1个回答

6
调用Invoke的开销仅在于它涉及线程上下文切换和(可能)等待UI线程可用。但是,如果您的UI线程没有忙于做大量其他工作,并且您不尝试每秒进行数百次picturebox更新,则不太可能出现明显问题。如果图像具有任何重要大小,则绘制像素将需要花费大量时间,使得Invoke所需的微秒数量微不足道。
更重要的是,您必须在UI线程上执行更新。因此,您要么在UI线程上执行所有处理,要么使用Invoke将更新传输到UI线程。由于您不希望占用UI线程进行计算,因此您实际上没有选择。
img作为函数参数或类成员之间的差异微不足道。 BeginInvoke可能很有用,但您必须小心。 BeginInvoke所做的只是排队请求,以便您的后台线程可以继续工作,UI线程可以在有时间时更新其显示。通过BeginInvoke排队如此多的请求非常容易,以至于UI线程花费接近其时间来处理这些请求,而其余UI似乎因用户发起的操作被塞入队列中的更新操作后而锁定。如果您每秒进行许多十几次图像更新,当然取决于图像大小,则您的UI线程永远无法赶上。您的UI将锁定,并最终会耗尽内存以排队请求。
在这里的主要性能瓶颈似乎是对图像进行计算,然后显示图像。这两个操作都非常耗时,以至于您在Invoke中花费的任何时间都可能不相关。
如果您的程序使用Invoke的性能足够好,则最好将其保留为原样。您可以尝试使用BeginInvoke,但是正如您所说,这将需要克隆图像。此外,您可能会遇到锁定的UI。
简而言之,对于您的问题的答案是“取决于情况”。我不了解您的图像处理或完成该处理所需的时间。如果您感到好奇,请尝试使用BeginInvoke并查看。最坏的情况是您的程序崩溃,您必须返回使用Invoke。但一定要在长时间内进行彻底测试。
您的最后一个问题不清楚。如果您问是否在后台线程上进行UI更新是否危险,则答案是“是!”在除UI线程以外的任何线程上更新UI可能会导致各种奇怪和奇妙的错误,有时几乎不可能复制,并且非常难以跟踪。 Windows期望UI元素在单个线程上更新。

1
解决BeginInvoke的问题的方法是手动限制自己的请求,这样如果图像没有在你有新图像要显示的时候被重新绘制,你就跳过除最新请求之外的所有请求。这样UI不会落后于处理,其他UI事件仍然不需要等待超过一个重绘时间才能到达队列的前面。当然,这种限制并不是非常容易管理的,至少没有像Rx这样的框架帮助。 - Servy
感谢您的宝贵帮助!因此,调用Invoke似乎是可用的最佳选择。我并没有认为打印本身的过程很重要。事实上,到目前为止,我得到的总体性能很好,但只是想确保我已经做出了正确的选择,以便在未来的修改增加负担时有足够的余地。您是对的,我的第四点不清楚:在某个时候,我只是问自己,所有这些关于线程安全的担忧是否合理,因为UI线程只需读取数据而不触及它? - Giuseppe Dini

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接