views:

134

answers:

4

Hi,

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

A: 

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

Hal
Thanks. I just updated the post adding some info regarding that.
pedroruiz
+2  A: 

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.

chibacity
pedroruiz
@pedroruiz That link is not working for me, so I cannot review how that blocking queue behaves. Try using a ConcurrentQueue, if you are using .Net 4.0 that is.
chibacity
Unfortunately I'm on Net 3.5.However, if that BlockingQueue implementation were to block readers until there is available data, wouldn't be my GUI always freezing?For instance if there are only messages arriving every 3 seconds it doesn't freeze. It's only when there are more than 1 a second or so.Thanks
pedroruiz
@pedroruiz I see. Yes you are correct, if it blocked readers when the queue was empty, then your GUI would always freeze when there was no data. BTW the link you added for the queue implementation is still no good.
chibacity
Oops, this is the codehttp://www.codeproject.com/KB/recipes/boundedblockingqueue.aspx
pedroruiz
@pedrorui The implementation does block readers when it is empty, you are specifying a timeout though, so you will not block indefinitely. I am curious to know why you are not tripping up on any QueueTimeoutException exceptions though as presumably sometimes when your timer fires, there is nothing in the queue and so you will timeout?
chibacity
@pedrorui Have you tried using TryDequeue instead Dequeue?
chibacity
+1  A: 
  1. Use threads to do background calculation stuff. (BackGroundWorker)
  2. Instead of updating the screen on each new event, store the data. Run a timer so that every X times a sec, it writes the current data to screen. The screen needs to change in order for people to see that something is coming in, not to show completely up to date information hundreds of times a second.
Carlos
A: 

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:

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
    }
DevExpress Team