【问题标题】:.NET GUI freezes when big load of data.NET GUI 在大量数据加载时冻结
【发布时间】:2010-07-07 21:10:36
【问题描述】:

我是一名使用 .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?

我认为这不是 BackgroundWorker,因为 GUI 是应该完成工作的,但也许我错了。

编辑1:

其实我有两个线程:

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

一旦主线程收到消息,它基本上会更新 Grid 和输出文本框,仅此而已。

    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控件

【问题讨论】:

标签: .net user-interface devexpress


【解决方案1】:

消息处理和 GUI 的常见反模式是在收到每条消息时立即响应。更好的方法通常是在收到消息时对其进行排队,然后仅定期更新 GUI,例如每 250 毫秒在一个计时器上。当您确实来更新 UI 时,请使用有效的更新方法。许多专业的 UI 组件都有“BeginUpdate\EndUpdate”的概念,可以应用一批更改,而无需在每次应用更改时更新 UI。

更新

您不应该使用 ConcurrentQueue 吗? BlockingQueue 将阻止阅读器(即在您的情况下,从外观上看是 UI),直到有可用数据为止。

【讨论】:

  • 可能是这个原因,我正在使用这些队列:codeproject.com/KB/recipes/…
  • @pedroruiz 该链接对我不起作用,因此我无法查看该阻塞队列的行为方式。如果您使用的是 .Net 4.0,请尝试使用 ConcurrentQueue。
  • 不幸的是,我使用的是 Net 3.5。但是,如果 BlockingQueue 实现要阻止读取器直到有可用数据,我的 GUI 不会总是冻结吗?例如,如果只有每 3 秒到达的消息,它就不会冻结。仅当每秒超过 1 次时。谢谢
  • @pedroruiz 我明白了。是的,你是对的,如果它在队列为空时阻止了阅读器,那么当没有数据时你的 GUI 总是会冻结。顺便说一句,您为队列实现添加的链接仍然不好。
【解决方案2】:
  1. 使用线程进行后台计算。 (BackGroundWorker)
  2. 不是在每个新事件上更新屏幕,而是存储数据。运行一个计时器,以便每秒 X 次将当前数据写入屏幕。屏幕需要改变,以便人们看到有东西进来,而不是每秒数百次完全显示最新信息。

【讨论】:

    【解决方案3】:

    您的消息接收代码占用多少 CPU 资源?

    尝试使用 BackgroundWorker 线程来处理消息接收,然后才更新 GUI。

    This 应该会帮助你。

    干杯

    【讨论】:

    • 谢谢。我刚刚更新了帖子,添加了一些相关信息。
    【解决方案4】:

    我认为对您来说一个好方法是使用 XtraGrid 的教程之一中显示的解决方案。请查看 Demos\Components\XtraGrid\CS\GridTutorials\GridVirtualData 文件夹。它包含演示项目,展示了如何有效地处理大数据。在此示例中,网格在包含 100000 条记录的集合中运行得非常快。网格行由索引表示,实际值使用属性描述符获得。因此,当加载网格时,它只获取屏幕上可见的行。滚动网格时,会动态访问其他数据。在getter中设置断点

    对象 IList.this[int fIndex]

    财产,您将看到电网有多智能:)。

    要解冻 GUI,可以使用 Application.DoEvents() 方法。

    我看到您正在使用 MemoEdit 来记录输入消息。它内部包含标准的多行文本框,并且该控件在内容大时运行速度非常慢:(。如果我正确理解您的任务,您已添加编辑器以允许最终用户复制输入消息。如果我是您,我会替换XtraGrid 的 MemoEdit。它允许您将数据从多个选定的记录复制到剪贴板。

    我们对演示项目稍作改动,这是我们最终得到的结果代码:

    List<LogMessage> list = new List<LogMessage>();
    for(int i = 0;i < 100000;i++) 
        list.Add(new LogMessage());
    vList = new VirtualList(list);
    grid.DataSource = vList;
    

    ...

        public class LogMessage {
            public LogMessage() {
                TimeStamp = DateTime.Now;
                Description = "Message at " + TimeStamp.Ticks.ToString();
            }
            public DateTime TimeStamp;
            public string Description;
        }
    
        public abstract class LogMessagePropertyDescriptor : PropertyDescriptor {
            bool fIsReadOnly;
    
            public LogMessagePropertyDescriptor(string fPropertyName, bool fIsReadOnly)
                : base(fPropertyName, null) {
                this.fIsReadOnly = fIsReadOnly;
            }
    
            public override bool CanResetValue(object component) { return false; }
            public override bool IsReadOnly {get { return fIsReadOnly; } }
            public override Type ComponentType { get { return typeof(LogMessage); } }
            public override void ResetValue(object component) {}
            public override bool ShouldSerializeValue(object component) { return true; }
        }
        public class LogMessageTimeStampPropertyDescriptor: LogMessagePropertyDescriptor {
            public LogMessageTimeStampPropertyDescriptor(bool fIsReadOnly)
                : base("TimeStamp", fIsReadOnly) {
            }
            public override Type PropertyType {get {return typeof(DateTime); } }
            public override object GetValue(object component) {
                LogMessage message = (LogMessage)component;
                return message.TimeStamp;
            }
            public override void SetValue(object component, object val) {
                LogMessage message = (LogMessage)component;
                message.TimeStamp = (DateTime)val;
            }
        }
        public class LogMessageDescriptionPropertyDescriptor: LogMessagePropertyDescriptor {
            public LogMessageDescriptionPropertyDescriptor(bool fIsReadOnly)
                : base("Description", fIsReadOnly) {
            }
    
            public override Type PropertyType { get { return typeof(string); } }
    
            public override object GetValue(object component) {
                LogMessage message = (LogMessage)component;
                return message.Description;
            }
            public override void SetValue(object component, object val) {
                LogMessage message = (LogMessage)component;
                message.Description = (string)val;
            }
        }
        public class VirtualList : IList, ITypedList {
            PropertyDescriptorCollection fColumnCollection;
            List<LogMessage> messages;
    
            public VirtualList(List<LogMessage> messages) {
                this.messages = messages;
                CreateColumnCollection();
            }
            public int RecordCount { get {return messages.Count; } }
            public int ColumnCount { get { return fColumnCollection.Count; } }
            protected virtual void CreateColumnCollection() {
                List<PropertyDescriptor> pds = new List<PropertyDescriptor>();
                pds.Add(new LogMessageTimeStampPropertyDescriptor(true));
                pds.Add(new LogMessageDescriptionPropertyDescriptor(false));
                fColumnCollection = new PropertyDescriptorCollection(pds.ToArray());
            }
    
            #region ITypedList Interface
            object IList.this[int fIndex] { get { return messages[fIndex]; } set { } }
            PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] descs) { return fColumnCollection; }
            string ITypedList.GetListName(PropertyDescriptor[] descs) { return ""; }
            #endregion
            #region IList Interface
            public virtual int Count { get { return RecordCount; } }
            public virtual bool IsSynchronized { get { return true; } }
            public virtual object SyncRoot { get { return true; } }
            public virtual bool IsReadOnly{ get { return false; } }
            public virtual bool IsFixedSize{ get { return true; } }
            public virtual IEnumerator GetEnumerator() { return null; }
            public virtual void CopyTo(System.Array array, int fIndex) {}
            public virtual int Add(object val) { throw new NotImplementedException(); }
            public virtual void Clear() { throw new NotImplementedException(); }
            public virtual bool Contains(object val) { throw new NotImplementedException(); }
            public virtual int IndexOf(object val) { throw new NotImplementedException(); }
            public virtual void Insert(int fIndex, object val) { throw new NotImplementedException(); }
            public virtual void Remove(object val) { throw new NotImplementedException(); }
            public virtual void RemoveAt(int fIndex) { throw new NotImplementedException(); }
            #endregion
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-05
      • 1970-01-01
      • 2014-07-29
      • 1970-01-01
      相关资源
      最近更新 更多