Хранение UI, быстро реагирующего при парсинге очень большого файла журнала

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

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

Вот код в форме:

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();
                               }
                    )); 
}

Какие потребности быть измененным?

Править

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 там. Немного тестирования других комментариев...

1
задан Carlos 25 May 2010 в 17:17
поделиться

4 ответа

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

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

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

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

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

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); 
        } 
    }
1
ответ дан 3 September 2019 в 00:19
поделиться

Первое, что вам нужно сделать, это включить двойную буферизацию в 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
ответ дан 3 September 2019 в 00:19
поделиться

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

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

0
ответ дан 3 September 2019 в 00:19
поделиться

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

0
ответ дан 3 September 2019 в 00:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: