我正在编写一个WPF
应用程序(使用MVVM
的{{1}}模式)来读取和显示公司使用的一堆内部日志文件。目的是读取多个文件,从每一行提取内容,将其放入类对象中,然后将所述对象添加到MVVM Light Toolkit
中。我已经将ObservableCollection
上ItemsSource
的{{1}}设置为此列表,以便它以整齐的行和列显示数据。我在第二个窗口中有一个DataGrid
控件,该控件在文件读取和显示过程中将更新进度。
请注意,所有这些方法都精简到删除所有无关代码位的要点。
加载按钮
当用户选择包含日志文件的目录并单击此按钮时,该过程开始。此时,我打开包含GUI
的窗口。我在此过程中使用了ProgressBar
。
ProgressBar
ProcessFiles()方法
这会读取所选目录中的所有文件,并逐一处理它们。在这里,启动进度条窗口时,我使用的是BackgroundWorker
。
public void LoadButtonClicked()
{
_dialogService = new DialogService();
BackgroundWorker worker = new BackgroundWorker
{
WorkerReportsProgress = true
};
worker.DoWork += ProcessFiles;
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerAsync();
}
ProcessOneFile()方法
顾名思义,它读取一个文件,逐行处理,将内容转换为我的类对象,然后将它们添加到列表中。
Dispatcher.Invoke()
所以这很好用,并根据需要显示我的日志。
但是,之后显示它们,如果我滚动private void ProcessFiles(object sender, DoWorkEventArgs e)
{
LogLineList = new ObservableCollection<LogLine>();
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
_dialogService.ShowProgressBarDialog();
});
var fileCount = 0;
foreach (string file in FileList)
{
fileCount++;
int currProgress = Convert.ToInt32(fileCount / (double)FileList.Length * 100);
ProcessOneFile(file);
(sender as BackgroundWorker).ReportProgress(currProgress);
}
}
,public void ProcessOneFile(string fileName)
{
if (FileIO.OpenAndReadAllLinesInFile(fileName, out List<string> strLineList))
{
foreach (string line in strLineList)
{
if (CreateLogLine(line, out LogLine logLine))
{
if (logLine.IsRobotLog)
{
LogLineList.Add(logLine);
}
}
}
}
}
会挂起并给出以下异常。
System.InvalidOperationException:'ItemsControl不一致 及其项目来源。有关更多信息,请参见内部异常 信息。
在SO上对此进行了阅读之后,我发现这是因为我的DataGrid
与GUI
不一致导致冲突。
当前解决方案
我发现,如果将代码行放在LogLineList
中,在那里我在第二个ItemsSource
中将类对象添加到列表中,则可以解决我的问题。像这样:
ProcessOneFile
现在这再次可以正常工作,但是问题是这极大地减慢了处理时间。以前包含10,000行的日志文件大约需要1秒钟,而现在所需的时间可能是原来的5-10倍。
我是在做错什么,还是可以预料的?有更好的方法来解决这个问题吗?
答案 0 :(得分:1)
可观察的集合不是线程安全的。因此,它是第二种方法,因为所有工作都是通过分派器在UI线程上完成的。
您可以使用异步操作来简化这种类型的流程。通过等待结果并更新结果的collection \ progress,您将保持UI响应和代码干净。
如果您不能或不想使用异步操作,请将更新批处理到集合中,然后在UI线程上进行更新。
编辑 像这样的例子
private async void Button_Click(object sender, RoutedEventArgs e)
{
//dir contents
var files = new string[4] { "file1", "file2", "file3", "file4" };
//progress bar for each file
Pg.Value = 0;
Pg.Maximum = files.Length;
foreach(var file in files)
{
await ProcessOneFile(file, entries =>
{
foreach(var entry in entries)
{
LogEntries.Add(entry);
}
});
Pg.Value++;
}
}
public async Task ProcessOneFile(string fileName, Action<List<string>> onEntryBatch)
{
//Get the lines
var lines = await Task.Run(() => GetRandom());
//the max amount of lines you want to update at once
var batchBuffer = new List<string>(100);
//Process lines
foreach (string line in lines)
{
//Create the line
if (CreateLogLine(line, out object logLine))
{
//do your check
if (logLine != null)
{
//add
batchBuffer.Add($"{fileName} -{logLine.ToString()}");
//check if we need to flush
if (batchBuffer.Count != batchBuffer.Capacity)
continue;
//update\flush
onEntryBatch(batchBuffer);
//clear
batchBuffer.Clear();
}
}
}
//One last flush
if(batchBuffer.Count > 0)
onEntryBatch(batchBuffer);
}
答案 1 :(得分:0)
public object SyncLock = new object();
在您的构造函数中:
BindingOperations.EnableCollectionSynchronization(LogLineList, SyncLock);
然后在您的函数中:
if (logLine.IsRobotLog)
{
lock(SyncLock)
{
LogLineList.Add(logLine);
}
}
这将使集合保持同步,无论您在哪个线程中对其进行更新。