调用 UI 元素时线程运行缓慢
本文关键字:运行 缓慢 线程 UI 元素 调用 | 更新日期: 2023-09-27 18:31:35
我正在编写一个基准测试工具,该工具从线程中的本地服务器读取一堆变量。
int countReads = 1000;
Int64 count = 0;
for (int i = 0; i < countReads; i++)
{
Thread.CurrentThread.Priority = ThreadPriority.Highest;
DateTime start = DateTime.Now;
session.Read(null, 0, TimestampsToReturn.Neither, idCollection, out ReadResults, out diagnosticInfos);
DateTime stop = DateTime.Now;
Thread.CurrentThread.Priority = ThreadPriority.Normal;
TimeSpan delay = (stop - start);
double s = delay.TotalMilliseconds;
count += (Int64)s;
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
{
progressBar1.Value = i;
}));
}
double avg = (double)count / countReads;
Dispatcher.Invoke(DispatcherPriority.Input, new Action(() =>
{
listBox1.Items.Add(avg);
}));
我正在计算继续读取所需的时间跨度,并在最后获得平均时间跨度。
DateTime start = DateTime.Now;
session.Read(null, 0, TimestampsToReturn.Neither, idCollection, out ReadResults, out diagnosticInfos);
DateTime stop = DateTime.Now
如果我在不更新进度条的情况下运行代码,平均需要大约 5 毫秒。但是如果我运行它
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
{
progressBar1.Value = i;
}));
平均需要大约 10 毫秒。
我的问题是,为什么使用进度条时时间跨度更高?我只是在计算读取的时间跨度。不包括进度条更新。
有没有办法疏散 ui 绘画,使其不会影响我的阅读时间跨度?
感谢您的帮助。
此致敬意
停止使用 Invoke
将进度信息传输到 UI 线程。将进度信息发布到共享数据结构或变量,并让 UI 线程按合理的时间间隔使用计时器轮询它。我知道我们似乎都被洗脑了,认为Invoke
是进行工作人员到 UI 线程交互的全部方法,但对于简单的进度信息,它可能(而且通常是)最糟糕的方法。
在 UI 线程上使用计时器的轮询方法具有以下优点。
- 它打破了
Invoke
对 UI 和工作线程施加的紧密耦合。 - UI 线程可以指示何时以及多久更新一次进度信息,而不是相反。当你停下来想一想时,无论如何它应该是这样的。
- 您可以在 UI 和工作线程上获得更多吞吐量。
我知道这并不能直接回答你关于为什么session.Read
似乎运行得更慢的问题。尝试将进度信息从推送模型(通过 Invoke
)更新为拉取模型(通过计时器)的策略。看看这是否有所作为。即使没有,由于上面列出的原因,我仍然会坚持使用拉动模型。
以下是 MSDN 对 Dispatcher.Invoke 的评价
。在与调度程序关联的线程上同步执行指定的委托。
因此,基本上,Dispatcher.Invoke
块,直到调度程序线程处理请求。
请尝试Dispatcher.BeginInvoke
。
我来晚了 9 年,但我认为这是一个更简单的解决方案:只需等到进度条值达到某个阈值再更新即可。在我的示例中,我每最大值的五分之一刷新一次工具栏。
private static int progressBarMaxValue = -1;
private static int progressBarChunkSize = -1;
public static void progressBarSetNotRealTimeValue(ProgressBar progressBar, int argNewValue)
{
if (progressBarMaxValue != -1)
{
if (argNewValue < progressBarChunkSize)
{
//Threshold not reached yet, discard the new value.
return;
}
else
{
//Allow the update, and set the next threshold higher.
progressBarChunkSize += progressBarChunkSize;
}
}
if (Thread.CurrentThread.IsBackground)
{
progressBar.BeginInvoke(new Action(() =>
{
if (progressBarMaxValue == -1)
{
progressBarMaxValue = progressBar.Maximum;
progressBarChunkSize = progressBar.Maximum / 5;
}
progressBar.Value = argNewValue;
}));
}
else
{
progressBar.Value = argNewValue;
}
}
如果当前正在执行的线程与您正在使用的调度程序相关联 - Invoke()
将阻止此线程,因此在这种情况下,尝试使用 Dispatcher.BeginInvoke() 它将异步完成作业。
MSDN, Dispatcher.Invoke Method:
调用是同步操作;因此,控件不会返回 到调用对象,直到回调返回。
顺便说一句,只是有兴趣尝试DispatcherPriority.Send