.NET GUI 在大量数据加载时冻结
我是一名使用 .NET 框架的初级开发人员。
我正在处理一个问题,因为当我运行带有大量数据的应用程序时,我的 GUI 冻结了。
我有一个网格和一种用于记录字符串的输出文本框。网格中每条预期到达的消息都有一行。
然后,应用程序接收消息,网格更新与该消息对应的行中的单元格。另外,我在文本框中写入一个字符串,其中包含有关该消息的信息。
例如,文本框将包含以下消息:
10:23:45 Message 1 arrived and the result is OK
10:23:45 Message 2 arrived and the result is OK
10:23:45 Message 3 arrived and the result is FAIL
10:23:45 Message 4 arrived and the result is OK
10:23:46 Message 5 arrived and the result is OK
....
网格将类似于:
MESSAGE_ID | RESULT <------- HEADER
Message_1 | OK
Message_2 | FAIL
Message_3 | OK
Message_4 | OK
Message_5 | OK
Message_6 | Waiting
Message_7 | Waiting
....
问题是,当我在很短的时间内收到多条消息时,GUI 会冻结,因为它一直在更新网格和文本盒子。它会冻结,直到所有消息到达并且网格和文本输出更新为止。
你知道是否有某种方法可以做到这一点,并且 GUI 不会冻结?使用多个线程来更新 GUI?
我认为这不是一个后台工作程序,因为 GUI 是应该完成这项工作的人,但也许我错了。
EDITED1:
事实上我有两个线程:
1)主线程。它是 GUI,它有一个 BlockingQueue。
private BlockingQueue _guiQueue = new BlockingQueue(1000);
2) 线程1 它接收消息,在接收到消息后执行一些工作,然后将结果排队并将其发送到 GUI:
_guiQueue.Enqueue(new UpdateResult(_message.Name, _message.Result));
我正在使用 BlockingQueues,这个: http://www.codeproject.com/KB/recipes/boundedblockingqueue.aspx
一旦主线程收到消息,它基本上会更新网格和输出文本框,仅此而已。
public MainThread(IMainForm mainView)
{
// presenter
_mainView = mainView;
....
// Blocking queues
_guiQueue = new BlockingQueue(1000);
....
// Timer
logger.Debug("Initializing Timer");
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(10);
// Call handleMessages method everytime the timer wakes up
_timer.Tick += HandleMessages;
_timer.Start();
...
// Order Passing Thread
logger.Debug("Launching OPThread");
_orderPassingThread = new OPThread(_OPQueue, _commonObjects);
_orderPassingThreadProcess = new Thread(new ThreadStart(_orderPassingThread.OPThreadProcess));
_orderPassingThreadProcess.Start();
...
}
private void HandleMessages(Object sender, EventArgs args)
{
Presenter.Messages.Message message;
while ((message = _guiQueue.Dequeue(10)) != null)
{
switch (message.MessageType)
{
case messageTypes.updateResult:
UpdateResult updateStepMsg = (UpdateResult) message;
_mainView.updateStepResult(updateStepMsg.Name, updateStepMsg.Result); // updates Grid and text box
break;
....
default:
break;
}
}
}
问题
是当我每秒左右收到多条消息时。
例如,我有一个“停止”按钮来停止一切,但无法单击它,因为 GUI 被冻结了,
谢谢!
PS:我使用的是DevExpress,网格是XtraGrid,输出文本框是memoEdit控件
I'm a junior developer using .NET framework.
I'm dealing with an issue because my GUI freezes when I run my application with big load of data.
I have a grid an a sort of output text box to log strings. The grid has a row for every message expected to arrive.
Then, the application receives messages and the grid updates a cell in the row that corresponds to the message. Also, I write a string in the text box with info about the message.
For example, the textbox will have messages such as:
10:23:45 Message 1 arrived and the result is OK
10:23:45 Message 2 arrived and the result is OK
10:23:45 Message 3 arrived and the result is FAIL
10:23:45 Message 4 arrived and the result is OK
10:23:46 Message 5 arrived and the result is OK
....
And the grid would be something like:
MESSAGE_ID | RESULT <------- HEADER
Message_1 | OK
Message_2 | FAIL
Message_3 | OK
Message_4 | OK
Message_5 | OK
Message_6 | Waiting
Message_7 | Waiting
....
The problem is that when I receive several messages in a very short of time, the GUI freezes because it is all the time updating the grid and the text box. It freezes until all the messages have arrived and the grid and text output are updated.
Do you know if there is some way to do this in some way that the GUI doesn't freeze? using more than one thread to update the GUI?
I think this is not a BackgroundWorker because the GUI is the one that should do the work but maybe I'm wrong.
EDITED1:
In fact I have a two threads:
1) Main Thread. It's the GUI and it has a BlockingQueue.
private BlockingQueue _guiQueue = new BlockingQueue(1000);
2) Thread1
It receives the messages, does some work after the message is received, and then it queues the result and send it to the GUI:
_guiQueue.Enqueue(new UpdateResult(_message.Name, _message.Result));
I'm using BlockingQueues, this ones:
http://www.codeproject.com/KB/recipes/boundedblockingqueue.aspx
Once Main Thread receives the message, it basically updates the Grid and the output text box, nothing else.
public MainThread(IMainForm mainView)
{
// presenter
_mainView = mainView;
....
// Blocking queues
_guiQueue = new BlockingQueue(1000);
....
// Timer
logger.Debug("Initializing Timer");
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromMilliseconds(10);
// Call handleMessages method everytime the timer wakes up
_timer.Tick += HandleMessages;
_timer.Start();
...
// Order Passing Thread
logger.Debug("Launching OPThread");
_orderPassingThread = new OPThread(_OPQueue, _commonObjects);
_orderPassingThreadProcess = new Thread(new ThreadStart(_orderPassingThread.OPThreadProcess));
_orderPassingThreadProcess.Start();
...
}
private void HandleMessages(Object sender, EventArgs args)
{
Presenter.Messages.Message message;
while ((message = _guiQueue.Dequeue(10)) != null)
{
switch (message.MessageType)
{
case messageTypes.updateResult:
UpdateResult updateStepMsg = (UpdateResult) message;
_mainView.updateStepResult(updateStepMsg.Name, updateStepMsg.Result); // updates Grid and text box
break;
....
default:
break;
}
}
}
}
The problem is when I receive more than one message a second or so.
By instance, I have a STOP button to stop everything, but there is no way to click on it because the GUI is freeze
Thanks!
PS: I'm using DevExpress, the grid is XtraGrid and the output text box is a memoEdit control
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
消息处理和 GUI 的常见反模式是立即响应收到的每条消息。更好的方法通常是在收到消息时对消息进行排队,然后仅定期更新 GUI,例如每 250 毫秒在计时器上更新一次。当您确实需要更新 UI 时,请使用有效的更新方法。许多专业的 UI 组件都有“BeginUpdate\EndUpdate”的概念,其中可以应用一批更改,而无需在应用每个更改时更新 UI。
更新
你不应该使用 ConcurrentQueue 吗? BlockingQueue 将阻塞读取器(即在您的情况下,从外观上看是 UI),直到有可用数据为止。
A common anti-pattern with message processing and GUIs is to respond immediately to every message as it's received. A better approach is often to queue messages as they are received and then only update the GUI periodically e.g. on a timer every 250ms. When you do come around to update the UI, use an efficient method of updating it. Many professional UI components have the concept of "BeginUpdate\EndUpdate", where a batch of changes can be applied without the UI updating for each and every change as it is applied.
Update
Shouldn't you be using a ConcurrentQueue? A BlockingQueue will block readers (i.e. in your case the UI by the looks of it) until there is available data.
您的消息接收代码的 CPU 密集程度如何?
尝试使用BackgroundWorker 线程来处理消息接收,然后才更新GUI。
这应该对您有所帮助。
干杯
How CPU intensive is your message receival code?
Try using a BackgroundWorker thread to process the message receival and only then update the GUI.
This should help you on your way.
Cheers
我认为对您来说一个好的方法是使用 XtraGrid 教程之一中所示的解决方案。请查看 Demos\Components\XtraGrid\CS\GridTutorials\GridVirtualData 文件夹。它包含演示项目,展示如何有效地处理大数据。在此示例中,网格对于包含 100000 条记录的集合运行得非常快。网格行由索引表示,并且使用属性描述符获得实际值。因此,当加载网格时,它仅获取屏幕上可见的行。当网格滚动时,会动态访问附加数据。 属性的 getter 中设置断点
在对象 IList.this[int fIndex]
,您将看到网格有多智能:)。
要解冻 GUI,可以使用 Application.DoEvents() 方法。
我看到您正在使用 MemoEdit 来记录输入消息。它内部包含标准的多行文本框,并且该控件在处理大量内容时运行速度非常慢:(。如果我正确理解您的任务,您已经添加了编辑器以允许最终用户复制输入消息。如果我是您,我会替换XtraGrid 的 MemoEdit 允许您将多个选定记录中的数据复制到剪贴板。
我们对演示项目进行了轻微更改,这是我们最终得到的代码:
...
I think that a good approach for you would be to use the solution shown in one of the XtraGrid's tutorials. Please take a look at the Demos\Components\XtraGrid\CS\GridTutorials\GridVirtualData folder. It contains the demo project showing how to effectively work with large data. In this sample, the grid works really fast with collection containing 100000 records. The grid rows are represented by indexes and real values are obtained using property descriptors. Thus, when the grid is loaded, it fetches only rows visible on the screen. When the grid is scrolled, additional data is accessed dynamically. Set the breakpoint in the getter of the
object IList.this[int fIndex]
property and you will see how smart the grid can be :).
To unfreeze the GUI, it is possible to use the Application.DoEvents() method.
I see that you are using the MemoEdit to log input messages. It contains the standard multiline TextBox inside and this control works really slow with large content :(. If I understand your task correctly, you've added the editor to allow the end-user copy input messages. If I were you, I would replace the MemoEdit by the XtraGrid. It allows you to copy data to clipboard from several selected records.
We have slightly changed the demo project and here is the resulting code we have finally got:
...