【发布时间】:2011-07-04 08:56:15
【问题描述】:
我有一个 TreeView 控件,其中填充了项目(特定目录中的每个文件一个),以及一个在后台运行的线程,它用额外的数据缓慢地填充树中的每个节点。
然后我需要 TreeViewNode 根据结果更新其文本字段。
我尝试在处理每个项目后在工作人员上使用 BeginInvoke,但这会导致 GUI 线程变得无响应,因为后台工作人员正在处理的节点数量过多。 C# - Updating GUI using non-main Thread
public static void InvokeIfRequired(this System.Windows.Forms.Control c,
Action action) {
if (c.InvokeRequired) {
c.Invoke((Action)(() => action()));
}
else {
action();
}
}
我尝试将多个节点更新批量处理到 GUI 的单个工作项中,并使用 BeginInvoke 一次更新多个,在此期间后台线程等待信号继续其工作,但这也会导致 GUI在 Invoked 方法期间变得无响应(似乎更新 TreeViewNode 是一项相当昂贵的操作?)
最后,我在 GUI 线程上创建了一个队列,由工作线程填充,并在应用程序空闲时进行轮询,效果很好,但感觉就像一个完整的 hack。
using System;
using System.Runtime.InteropServices;
namespace System
{
struct PointAPI
{
public Int32 x;
public Int32 y;
}
struct WindowsMessage
{
public Int32 hwnd;
public Int32 message;
public Int32 wParam;
public Int32 lParam;
public Int32 time;
public PointAPI pt;
}
static class IdlePolling
{
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PeekMessage(ref WindowsMessage lpMsg,
Int32 hwnd,
Int32 wMsgFilterMin,
Int32 wMsgFilterMax,
Int32 wRemoveMsg);
static public bool HasEventsPending()
{
WindowsMessage msg = new WindowsMessage();
return PeekMessage(ref msg, 0, 0, 0, 0);
}
}
}
...
Application.Idle += mainForm.OnIdle;
...
public partial class MainWindow : Form
{
...
public void OnIdle(object sender, EventArgs e)
{
while (IdlePolling.HasEventsPending() == false)
{
ConsumeGUIUpdateItem();
Thread.Sleep(50);
}
}
}
那么更新大量gui项目的“正确”方法是什么,不会导致GUI线程在进程中挂起。
【问题讨论】:
-
对 UI 的经典滥用。 UI 是面向用户的,一个用户不能处理超过几十个项目,所以你不应该加载数千个。
-
过去我只是简单地编写了一些代码来将每次更新的事件推送到 UI 线程中。它非常适合每秒 100 次更新。
-
@Henk - 可能会突然添加项目,让用户有足够的时间与 treeView 进行交互?奇怪的是,UI 显然可以处理更新,因为 'complete hack' 有效,那么为什么 BeginInvoke 的性能如此糟糕?
-
“hack”有效,因为它只在主窗体的消息队列为空并且其中有一个 Sleep 时执行。这意味着一旦表单想要重新绘制,OnIdle 函数会在下一个项目完成更新后返回,并且在再次收到 Application.OnIdle 事件之前不会更新任何项目。
-
@MartinJames 我猜 BeginInvoke 性能很差,因为每一帧,后台线程可能已经要求主线程做 N 项工作,而 GUI 线程跟不上背负重担,忽略其他信息?
标签: c# multithreading user-interface