.NET GUI зависает при большой загрузке данных - PullRequest
3 голосов
/ 07 июля 2010

Я младший разработчик, использующий .NET Framework.

Я имею дело с проблемой, потому что мой 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
....

Проблема в том, что когда я получаю несколько сообщений за очень короткий промежуток времени, графический интерфейс пользователя зависает, потому что он постоянно обновляет сетку и текстовое поле.Он останавливается до тех пор, пока не будут получены все сообщения, а сетка и вывод текста не обновятся.

Знаете ли вы, есть ли какой-нибудь способ сделать это так, чтобы графический интерфейс не зависал?использовать более одного потока для обновления графического интерфейса?

Я думаю, что это не BackgroundWorker, потому что именно графический интерфейс должен выполнять эту работу, но, возможно, я ошибаюсь.

EDITED1:

На самом деле у меня естьдве темы:

1) Основная тема.Это GUI, и он имеет BlockingQueue.

private BlockingQueue _guiQueue = new BlockingQueue(1000);

2) Thread1 Он получает сообщения, выполняет некоторую работу после получения сообщения, а затем ставит в очередь результат и отправляет его в 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;
            }
        }
    }

}

Проблема в том, что я получаю более одного сообщения в секунду или около того.

Например, у меня есть кнопка СТОП, чтобы остановить все, но нет никакого способащелкнуть по нему, потому что графический интерфейс остановлен

Спасибо!

PS: я использую DevExpress, сетка - XtraGrid, а текстовое поле вывода - элемент управления memoEdit

Ответы [ 4 ]

2 голосов
/ 07 июля 2010

Обычный анти-шаблон с обработкой сообщений и графическим интерфейсом - это немедленный ответ на каждое сообщение по мере его поступления.Лучший подход часто состоит в том, чтобы ставить сообщения в очередь по мере их поступления, а затем обновлять GUI только периодически, например, по таймеру каждые 250 мс.Когда вы зайдете обновить пользовательский интерфейс, используйте эффективный способ его обновления.Многие профессиональные компоненты пользовательского интерфейса имеют концепцию «BeginUpdate \ EndUpdate», где пакет изменений может быть применен без обновления пользовательского интерфейса для каждого изменения по мере его применения.

Обновление

Разве вы не должны использовать ConcurrentQueue?BlockingQueue будет блокировать считыватели (т. Е. В вашем случае, по-видимому, пользовательский интерфейс) до тех пор, пока не появятся доступные данные.

1 голос
/ 07 июля 2010
  1. Используйте потоки, чтобы делать фоновые вычисления. (BackgroundWorker)
  2. Вместо обновления экрана при каждом новом событии сохраняйте данные. Запустите таймер, чтобы каждые X раз в секунду записывать текущие данные на экран. Экран должен измениться, чтобы люди могли видеть, что что-то идет, а не показывать полностью актуальную информацию сотни раз в секунду.
0 голосов
/ 07 июля 2010

Я думаю, что хорошим подходом для вас будет использование решения, показанного в одном из учебных пособий XtraGrid.Пожалуйста, обратите внимание на папку Demos \ Components \ XtraGrid \ CS \ GridTutorials \ GridVirtualData.Он содержит демонстрационный проект, показывающий, как эффективно работать с большими данными.В этом примере сетка работает очень быстро с коллекцией, содержащей 100000 записей.Строки сетки представлены индексами, а реальные значения получены с использованием дескрипторов свойств.Таким образом, когда сетка загружена, она выбирает только строки, видимые на экране.Когда сетка прокручивается, дополнительные данные доступны динамически.Установите точку останова в получателе свойства

объекта IList.this [int fIndex]

, и вы увидите, насколько умной может быть сетка:).

РазморозитьGUI, можно использовать метод Application.DoEvents ().

Я вижу, что вы используете MemoEdit для регистрации входных сообщений.Он содержит стандартный многострочный TextBox внутри, и этот элемент управления работает очень медленно с большим содержимым :(. Если я правильно понимаю вашу задачу, вы добавили редактор, позволяющий конечным пользователям копировать входные сообщения. Если бы я был вами, я бы заменилMemoEdit от XtraGrid. Он позволяет вам копировать данные в буфер обмена из нескольких выбранных записей.

Мы немного изменили демонстрационный проект, и вот окончательный код, который мы наконец получили:

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
    }
0 голосов
/ 07 июля 2010

Насколько загружен ваш код получения сообщений?

Попробуйте использовать поток BackgroundWorker для обработки получения сообщения и только затем обновите графический интерфейс.

Это должно помочь вам в вашем пути.

Приветствия

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...