Поддержание отзывчивости пользовательского интерфейса при анализе очень большого файла журнала - PullRequest
1 голос
/ 25 мая 2010

Я пишу приложение, которое анализирует очень большой файл журнала, чтобы пользователь мог просматривать содержимое в формате дерева. Я использовал BackGroundWorker для чтения файла, и когда он анализирует каждое сообщение, я использую BeginInvoke, чтобы получить поток GUI для добавления узла в мое древовидное представление. К сожалению, есть две проблемы:

  • Древовидное представление не реагирует на щелчки или прокрутки во время анализа файла. Я хотел бы, чтобы пользователи могли проверять (то есть расширять) узлы во время синтаксического анализа файла, чтобы им не приходилось ждать окончания анализа всего файла.
  • Вид дерева мигает каждый раз, когда добавляется новый узел.

Вот код внутри формы:

private void btnChangeDir_Click(object sender, EventArgs e)
{
    OpenFileDialog browser = new OpenFileDialog();

    if (browser.ShowDialog() == DialogResult.OK)
    {
        tbSearchDir.Text = browser.FileName;
        BackgroundWorker bgw = new BackgroundWorker();
        bgw.DoWork += (ob, evArgs) => ParseFile(tbSearchDir.Text);
        bgw.RunWorkerAsync();
    }
}

private void ParseFile(string inputfile)
{
    FileStream logFileStream = new FileStream(inputfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    StreamReader LogsFile = new StreamReader(logFileStream);

    while (!LogsFile.EndOfStream)
    {
        string Msgtxt = LogsFile.ReadLine();
        Message msg = new Message(Msgtxt.Substring(26)); //Reads the text into a class with appropriate members
        AddTreeViewNode(msg);
    }
}

private void AddTreeViewNode(Message msg)
{
    TreeNode newNode = new TreeNode(msg.SeqNum);

    BeginInvoke(new Action(() =>
                               {
                                   treeView1.BeginUpdate();
                                   treeView1.Nodes.Add(newNode);
                                   treeView1.EndUpdate();
                                   Refresh();
                               }
                    )); 
}

Что нужно изменить?

EDIT

New code, to replace the last function above:
        List<TreeNode> nodeQueue = new List<TreeNode>(1000);

        private void AddTreeViewNode(Message msg)
        {
            TreeNode newNode = new TreeNode(msg.SeqNum);

            nodeQueue.Add(newNode);

            if (nodeQueue.Count == 1000)
            {
                var buffer = nodeQueue.ToArray();
                nodeQueue.Clear();
                BeginInvoke(new Action(() =>
                                           {
                                               treeView1.BeginUpdate();
                                               treeView1.Nodes.AddRange(buffer);
                                               treeView1.EndUpdate();
                                               Refresh();
                                               Application.DoEvents();
                                           }
                                ));
            }
        }

Не уверен, почему я оставил там Обновить и DoEvents. Немного протестирую другие комментарии ...

Ответы [ 4 ]

1 голос
/ 25 мая 2010

Первое, что вам нужно сделать, это включить двойную буферизацию в TreeView, чтобы он прекратил мерцать. Это поддерживается с Vista, но, к сожалению, не Windows Forms. Добавьте новый класс в ваш проект и вставьте код, показанный ниже. Компиляция. Перетащите элемент управления с верхней части панели инструментов на форму.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class BufferedTreeView : TreeView {
    protected override void OnHandleCreated(EventArgs e) {
        base.OnHandleCreated(e);
        IntPtr style = (IntPtr)TVS_EX_DOUBLEBUFFER;
        SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)style, (IntPtr)style);
    }
    // P/Invoke:
    private const int TVS_EX_DOUBLEBUFFER = 0x004;
    private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44;
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}

Для обеспечения адаптивности вашего пользовательского интерфейса необходимо изменить метод ParseFile (). Как написано, он слишком часто вызывает BeginInvoke (). Это затопляет поток пользовательского интерфейса запросами, с которыми он не может справиться. Он больше не справляется со своими обычными обязанностями, такими как рисование и реагирование на щелчки мышью.

Это напрасная трата усилий, человеческий глаз не может воспринимать обновления, которые происходят со скоростью, превышающей 25 раз в секунду. Сохраните данные в коллекции BeginInvoke и передайте эту коллекцию с гораздо более низкой скоростью.

1 голос
/ 25 мая 2010

Invoke/BeginInvoke использует PostMessage внутренне для перенаправления запроса из произвольного потока в поток пользовательского интерфейса. BeginInvoke будет заполнять вашу очередь сообщений Windows сообщениями, которые должны обрабатываться потоком пользовательского интерфейса, что сопровождается тем фактом, что вы отключаете перерисовку дерева для каждого добавляемого вами узла, вероятно, влияет на вашу способность взаимодействовать с деревом, пока это загрузка.

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

Обновление: после вашего редактирования для добавления узлов в пакетах я бы предложил следующее.

1 - Вместо использования Invoke, а не BeginInvoke, в противном случае очередь заполняется во время обновления дерева, а затем, когда дерево обновляется, следующие тысячи узлов готовы для вставки, что возвращает вас обратно туда, где вы находитесь .

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

3- Обратите внимание, что ваше текущее пакетное решение пропустит несколько последних узлов, если общее число не кратно 1000

    private void AddTreeViewNode(Message msg) 
    { 
        TreeNode newNode = new TreeNode(msg.SeqNum); 

        nodeQueue.Add(newNode); 

        if (nodeQueue.Count == 1000) 
        { 
            var buffer = nodeQueue.ToArray(); 
            nodeQueue.Clear(); 
            Invoke(new Action(() => 
                { 
                    treeView1.BeginUpdate(); 
                    treeView1.Nodes.AddRange(buffer); 
                    treeView1.EndUpdate(); 
                }));
            System.Threading.Thread.Sleep(500); 
        } 
    }
0 голосов
/ 25 мая 2010

Я не запускал твой код с профилировщиком, но если ты считаешь медленный ввод-вывод ботлэнком, ты можешь попробовать MemoryMappedFile из .NET 4.0 .

Таким образом, вместо поиска и чтения построчно с диска у вас будет доступ к тем же данным в памяти вместо дискового ввода-вывода.

0 голосов
/ 25 мая 2010

вы пробовали Application.Doevents() метод?

...