C# ItemsControl与其items源不一致-使用Dispatcher.Invoke()时出现问题
我正在编写一个C# ItemsControl与其items源不一致-使用Dispatcher.Invoke()时出现问题,c#,wpf,dispatcher,itemscontrol,C#,Wpf,Dispatcher,Itemscontrol,我正在编写一个WPF应用程序(MVVM模式,使用mvvmlighttoolkit)来读取和显示我公司使用的一组内部日志文件。目标是从多个文件中读取,从每一行中提取内容,将它们放在一个类对象中,并将所述对象添加到一个observateCollection。我已将我的GUI上的DataGrid的ItemsSource设置到此列表,以便它以整齐的行和列显示数据。我在第二个窗口中有一个ProgressBar控件,在文件读取和显示过程中,该控件将更新进度 安装程序 请注意,所有这些方法都被简化为基本方法,
WPF
应用程序(MVVM
模式,使用mvvmlighttoolkit
)来读取和显示我公司使用的一组内部日志文件。目标是从多个文件中读取,从每一行中提取内容,将它们放在一个类对象中,并将所述对象添加到一个observateCollection
。我已将我的GUI
上的DataGrid
的ItemsSource
设置到此列表,以便它以整齐的行和列显示数据。我在第二个窗口中有一个ProgressBar
控件,在文件读取和显示过程中,该控件将更新进度
安装程序
请注意,所有这些方法都被简化为基本方法,删除了所有不相关的代码位
加载按钮
当用户选择包含日志文件的目录并单击此按钮时,过程开始。此时,我打开包含ProgressBar
的窗口。我使用BackgroundWorker
进行此过程
public void LoadButtonClicked()
{
_dialogService = new DialogService();
BackgroundWorker worker = new BackgroundWorker
{
WorkerReportsProgress = true
};
worker.DoWork += ProcessFiles;
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerAsync();
}
ProcessFiles()方法
这将读取所选目录中的所有文件,并逐一进行处理。在这里,当启动进度条窗口时,我使用的是Dispatcher.Invoke()
现在,这同样可以很好地工作,但问题是这会大大降低处理时间。以前,一个包含10000行的日志文件大约需要1s,而现在可能需要5-10倍的时间
我做错了什么,还是这是意料之中的事?有没有更好的方法来处理这个问题
public object SyncLock = new object();
在构造函数中:
BindingOperations.EnableCollectionSynchronization(LogLineList, SyncLock);
然后在您的函数中:
if (logLine.IsRobotLog)
{
lock(SyncLock)
{
LogLineList.Add(logLine);
}
}
这将使集合保持同步,无论您从哪个线程更新它。可观察的集合不是线程安全的。因此,它以第二种方式工作,因为所有工作都是通过dispatcher在UI线程上完成的 您可以使用异步操作来简化这种类型的流。通过等待结果并更新结果的集合\进度,您将保持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);
}
private async void按钮\u单击(对象发送方,路由目标)
{
//目录内容
var files=新字符串[4]{“file1”、“file2”、“file3”、“file4”};
//每个文件的进度条
Pg.值=0;
Pg.max=files.Length;
foreach(文件中的var文件)
{
等待ProcessOneFile(文件,条目=>
{
foreach(分录中的var分录)
{
日志条目。添加(条目);
}
});
Pg.Value++;
}
}
公共异步任务ProcessOneFile(字符串文件名,操作onEntryBatch)
{
//接电话
var lines=wait Task.Run(()=>GetRandom());
//一次要更新的最大行数
var batchBuffer=新列表(100);
//生产线
foreach(行中的字符串行)
{
//创建线
if(CreateLogLine(线,输出对象logLine))
{
//请结账
if(logLine!=null)
{
//加
Add($“{fileName}-{logLine.ToString()}”);
//检查一下我们是否需要冲洗
if(batchBuffer.Count!=batchBuffer.Capacity)
继续;
//更新\刷新
onEntryBatch(批缓冲区);
//清楚的
batchBuffer.Clear();
}
}
}
//最后一次冲水
如果(batchBuffer.Count>0)
onEntryBatch(批缓冲区);
}
通过等待结果并更新集合\结果进度,您将保持UI响应速度快,代码干净。那么,您的意思是我应该等待CreateLogLine()
方法并更新集合吗?您希望等待流程文件并使用操作回调成批更新集合。我用一个例子更新了我的答案。这没有帮助。我还是有问题。你是说速度问题还是同步问题?就速度而言,您是否尝试将锁移出循环?不是速度问题,在加载后,每当我尝试滚动时(或任何操作,如最大化窗口,导致重新绘制DataGrid
),锁仍会继续挂起。如果您可以批量更新而不是实时更新,则创建单独的集合(非obs)每5秒添加一次日志行-将项目源更改为从非obs集合创建的新obs集合LoadButtonClick方法位于查看代码背后?@shat90我将尝试一下,看看是否可行。@SirRufo,不,我使用的是MVVM
,所以它位于主窗口的ViewModel
中。该方法附加到按钮的click事件。@那么,您应该在ViewModel中定义ICommand属性,并将其绑定到按钮的Command属性。方法分配不是正确的MVVM绑定方式。仅供参考
BindingOperations.EnableCollectionSynchronization(LogLineList, SyncLock);
if (logLine.IsRobotLog)
{
lock(SyncLock)
{
LogLineList.Add(logLine);
}
}
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);
}