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

#c# #multithreading #winforms

#c# #многопоточность #winforms

Вопрос:

Я создал элемент управления для вывода сообщений из разных потоков на экран. Для отображения форматированного текста используется форматированное текстовое поле.

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

Запись сообщения в форматированное текстовое поле синхронизируется с блокировками.

Что вы можете предложить для повышения производительности? Я планирую запустить 100 потоков.


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

 public void RedirectStandardOutput()
        {
            Console.SetOut(ConsoleStream);

            System.Diagnostics.Debug.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(Console.Out));
            System.Diagnostics.Debug.AutoFlush = true;
        }
  

После консоли перенаправляются все консоли.WriteLine («бла-бла»); записывается на экран.


 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using CoreLib.Parsers;

namespace ConsoleWidget
{
    public class ConsoleStream : System.IO.TextWriter
    {
        private readonly object _textBoxLock = new object();

        public RichTextBox TextBox { get; set; }
        public List<TextFormat> TextFormatList { get; set; }

        public bool AutoClear { get; set; }
        public int AutoClearLength { get; set; }
        public bool AutoSave { get; set; }
        public string AutoSaveDir { get; set; }


        public ConsoleStream()
        {
            TextFormatList = new List<TextFormat>();
        }

        public void AppendText(string text)
        {
            if (TextBox == null) return;

            var textLength = TextBox.TextLength;

            if (AutoClear amp;amp; textLength > AutoClearLength)
            {
                if (AutoSave)
                {
                    var dir = string.Format(@"{0}{1}{2}", Environment.CurrentDirectory, AutoSaveDir, CoreLib.Extensions.DateTimeExtensions.DateTimeNowDir);

                    if (!System.IO.Directory.Exists(dir))
                        System.IO.Directory.CreateDirectory(dir);

                    var path = string.Format(@"{0}{1}.log", dir, CoreLib.Extensions.DateTimeExtensions.GetDateTimeNowFileName);

                    TextBox.SaveFile(path);
                }

                TextBox.Clear();
            }

            TextBox.AppendText(text);

            // Format text.
            foreach (var textFormat in TextFormatList)
            {
                int beginIndex;
                int length;

                if (textFormat.GetFormatProperties(text, out beginIndex, out length))
                {
                    // RichTextBox counts newline "rn" which is double char as single char.
                    // Causes shifting in selection starts. The lines below count the "r" chars before the beginIndex.
                    var leftText = text.Substring(0, beginIndex);
                    var newLineCount = leftText.Count(c => c == 'r');

                    TextBox.SelectionStart = textLength   beginIndex - newLineCount;
                    TextBox.SelectionLength = length;
                    if (!textFormat.Color.IsEmpty)
                        TextBox.SelectionColor = textFormat.Color;
                    if (textFormat.Font != null)
                        TextBox.SelectionFont = textFormat.Font;
                }
            }

            TextBox.ScrollToCaret();
        }

        public void Clear()
        {
            lock (_textBoxLock)
            {
                TextBox.Clear();
            }
        }

        public int TextLength
        {
            get
            {
                lock (_textBoxLock)
                {
                    return TextBox.TextLength;
                }
            }
        }

        public void SaveAs(string path)
        {
            lock (_textBoxLock)
            {
                TextBox.SaveFile(path);
            }
        }

        public override Encoding Encoding
        {
            get { return Encoding.Defau< }
        }

        public override void Write(string value)
        {
            if (TextBox == null) return;

            var action = (Action)(() => AppendText(value));

            lock (_textBoxLock)
            {
                if (TextBox.InvokeRequired)
                    TextBox.BeginInvoke(action);
                else
                    action();
            }
        }

        public override void WriteLine()
        {
            Write(NewLine);
        }

        public override void WriteLine(string value)
        {
            Write(value);
            WriteLine();
        }
    }
}
  

Комментарии:

1. Можете ли вы поделиться кодом, который обновляет текстовое поле?

2. Как вы выполняете написание в разных потоках? Используете ли вы Control. Вызов, управление. BeginInvoke или вы проводите опрос сообщений в потоке пользовательского интерфейса?

3. Возможно, вам также захочется подумать об использовании чего-то другого, кроме расширенного текстового поля, для отображения данных. Я никогда не использовал его, поэтому я только предполагаю, но я подозреваю, что добавление текста будет происходить все медленнее и медленнее по мере увеличения размера текста в текстовом поле. Просто мысль….

4. Для меня эта проблема внезапно началась с момента запуска моего приложения в Windows 7. В XP все было в порядке : (

Ответ №1:

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

Комментарии:

1. Буфер какого-либо вида, отличный от того, которым управляет поток пользовательского интерфейса, определенно подходит здесь. Затем пользовательский интерфейс может иметь таймер, который запускает событие для обновления видимого компонента из буфера.

2. На этот раз мне нужно будет самому управлять состоянием объекта. Потребуется сохранять новые сообщения и добавлять их каждые 250 мс.

Ответ №2:

Попросите ваши рабочие потоки добавить свои данные в какую-то очередь / список, а затем попросите основной поток добавлять пакет новых данных из хранилища new data каждую секунду / полсекунды (настройте в соответствии с вашим процессом).

Что-то базовое, подобное этому, вероятно, было бы неплохо:

     public class DataStore<T>{
    private object _lock = new object();
    private List<T> _data = new List<T>();
    public void Add(T data){
        lock (_lock){
            _data.Add(data);
        }
    }

    public T[] TakeWork(){
        T[] resu< 
        lock (_lock){
            result= _data.ToArray(); 
            _data.Clear();
        }
        return resu<
    }
}
  

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

       foreach (var s in _dataStore.TakeWork()){
            richTextBox.AppendText(s);                  
        }
  

в системе.Windows.Формы.Событие таймера. Вы, вероятно, захотите также обрезать текст в поле форматированного текста, поскольку ваше приложение начнет замедляться, если вы будете просто загружать данные весь день….

Комментарии:

1. Здравствуйте, возможно, я немного опоздал, но я столкнулся с проблемой, когда, загружая огромные данные в форматированное текстовое поле, пользовательский интерфейс перестает отвечать. Добавление данных в RTB происходит в отдельном потоке. Любая зацепка была бы оценена.

Ответ №3:

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