Я пишу приложение, которое анализирует очень большой файл журнала, так, чтобы пользователь видел содержание в treeview формате. Я использовал BackGroundWorker для чтения файла, и поскольку он анализирует каждое сообщение, я использую BeginInvoke, чтобы заставить поток GUI добавлять узел к моему 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 там. Немного тестирования других комментариев...
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);
}
}
Первое, что вам нужно сделать, это включить двойную буферизацию в 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 и передайте эту коллекцию гораздо медленнее.
Я не запускал ваш код с помощью профилировщика, но если вы считаете медленный ввод-вывод ошибкой, вы можете попробовать MemoryMappedFile из .NET 4.0 .
Таким образом, вместо поиска и чтения с диска построчно у вас будет доступ к одним и тем же данным в памяти, а не к вводу-выводу на диске.