从单独的线程在表单上绘制图像
Posted
技术标签:
【中文标题】从单独的线程在表单上绘制图像【英文标题】:Draw image on a form from a separate thread 【发布时间】:2010-01-09 21:48:49 【问题描述】:我目前正在开发一个 Windows.Forms 应用程序。这基本上是一个简单的运动检测问题。
我在表单上有一个按钮,按下该按钮时会启动执行以下操作的后台工作人员:
-
从磁盘获取图像
创建一个新的位图,用作缓冲区。
执行运动检测
根据运动检测的结果,更新缓冲区(使用缓冲区的绘图表面)
使用包含缓冲区克隆的参数触发进度更改事件,基本上是
(sender as BackgroundWorker).ReportProgress((Bitmap)buffer.Clone())
在 Progress Changed 事件中,然后我将缓冲区绘制到屏幕上。
if (!PnlImage.IsDisposed)
PnlImage.CreateGraphics().DrawImageUnscaled(buffer, 0, 0);
我不禁想知道这是否是在屏幕上绘制更新图像的最佳方式。谁能建议我可以做的任何改进? 谢谢。
编辑: 我已经更新了程序以使用 .NET Framework 4,我们不再使用 BackgroundWorker。相反,我们现在使用 System.Threading.Tasks 命名空间,并使用 Invoke 从任务中更新背景图像。
感谢所有回复。
【问题讨论】:
克隆缓冲区的原因是什么? 你不能跨线程访问它,因为每一步都会创建和销毁一个新的缓冲区。我尝试制作一个公共缓冲区,并使用锁来访问它,但它并没有证明太成功。 【参考方案1】:我相信您可能遇到的任何问题的根源在于任何 GUI 更新都必须在 UI 线程上完成。您无法从另一个线程安全地更新 UI。因此,基本上,您需要执行以下操作(我只是以更改背景颜色为例,但您可以随心所欲):
private void SomethingCalledFromBackgroundThread()
panel1.Invoke(new DoUpdatePanel(UpdatePanel), Color.Blue);
private delegate void DoUpdatePanel(Color aColor);
private void UpdatePanel(Color aColor)
panel1.BackColor = aColor;
============更新=======>
@Ash 你误解了我的回答。我没有说从 ProgressChanged 中调用 Invoke。 @Jean 请记住,ReportProgress/ProgressChanged 正在异步运行——这就是为什么您发现自己正在克隆您的图像。如果您在后台线程中使用 Invoke,而不是 ReportProgress,则不需要这样做。
【讨论】:
【参考方案2】:我不确定这是否完全正确,但我确信您不能在单独的线程上跨线程 GUI/控制操作,因为默认情况下它是在专用的 GUI 线程上处理的。
我之前尝试做类似的事情,最后我决定采用一种完全不同的方法,因为将属性设置为 false 是让它工作的最糟糕的方法。
【讨论】:
我知道,这就是为什么我通过克隆将位图传递给 Progress Changed Event,因为跨线程 gui 更新只会导致由于跨线程访问而导致的异常。我只是想知道是否有人成功地做类似的事情 抱歉,我个人对 GUI 组件没有太多经验。如果您当前的方法有效,那么最好坚持使用它,尝试寻找替代方法可能会使其性能更差,而且您似乎做得很好。【参考方案3】:ProgressChanged 和 RunWorkerCompleted 事件允许您直接更新 UI。只有 DoWork 事件处理程序不能访问 from。 See MSDN:
你必须小心不要操纵 您的任何用户界面对象 DoWork 事件处理程序。反而, 与用户界面通信 通过 ProgressChanged 和 RunWorkerCompleted 事件。
这是使用 BackgroundWorker 创建自己的线程的主要好处之一。所以TheObjectGuy是不正确的,你不需要在ProgressChanged中使用BeginInvoke/Invoke。
只要您的图像不太大,克隆它就不会导致任何严重的性能问题。如果您有顾虑,请使用更大的图像运行一些性能测试。
否则,为避免使用锁等棘手的同步问题,我认为克隆图像是保持简单的好方法。
【讨论】:
图像大约为 640x480,并且几乎可以保证保持这种状态。不过还是谢谢。【参考方案4】:使用 ProgressChanged 事件很好。不好的是直接在屏幕上绘图。当您最小化并恢复表单时,您的图像将消失。解决方法很简单:
PnlImage.BackgroundImage = buffer;
【讨论】:
谢谢,但是,我们每秒在屏幕上绘制大约 23 次 :S 像地狱一样闪烁.. 那你就不用担心图片会消失了。 哦,但不是用 PictureBox 代替面板。感谢您的提示! 是的,PB 有 DoubleBuffer = true,Panel 没有。以上是关于从单独的线程在表单上绘制图像的主要内容,如果未能解决你的问题,请参考以下文章
Form.Owner 从 .NET 3.5 中的单独线程设置