当您这样做时,您正在放弃在后台执行垃圾收集的选项。换句话说,您的UI线程将被挂起,无论您是否从工作线程中执行此操作。唯一可能领先的方法是当GC.WaitForPendingFinalizers()需要大量时间时。实际上,这不是您应该等待的东西,没有任何意义,如果它花费的时间超过一眨眼那么您正在隐藏代码中相当严重的错误。
另一个重要的问题是,Windows工作站版本会给予拥有前景窗口的任何线程更长的时间片。换句话说,它可以比后台线程更长时间地使用核心资源。这是使Windows对用户更具响应性的一个简单技巧。
由于涉及的因素太多,最好的方法是测试您的理论,以确保在工作线程上运行垃圾回收确实能够提高性能。测量UI线程的挂起时间非常简单,您可以使用计时器来完成。其Tick事件无法在线程挂起时运行。开始一个新的Winforms项目,在窗体上放置一个计时器,将其间隔设置为1并启用,添加一个标签并使用此代码来测量延迟:
int prevtick = 0;
int maxtick = -1;
private void timer1_Tick(object sender, EventArgs e) {
int tick = Environment.TickCount;
if (prevtick > 0) {
int thistick = tick - prevtick;
if (thistick > maxtick) {
maxtick = thistick;
label1.Text = maxtick.ToString();
}
}
prevtick = tick;
}
运行程序,你应该在标签中看到数字16。如果你得到的数字小于16,那么你需要修理你的机器,不是其他任何影响测试的原因。添加一个按钮来重置测量:
private void button1_Click(object sender, EventArgs e) {
maxtick = -1;
}
添加一个复选框和另一个按钮。它将执行实际的收集:
private void button2_Click(object sender, EventArgs e) {
var useworker = checkBox1.Checked;
System.Threading.ThreadPool.QueueUserWorkItem((_) => {
var lst = new List<object>();
for (int ix = 0; ix < 500 * 1024 * 1024 / (IntPtr.Size * 3); ++ix) {
lst.Add(new object());
}
lst.Clear();
if (useworker) {
GC.Collect();
GC.WaitForPendingFinalizers();
}
else {
this.BeginInvoke(new Action(() => {
GC.Collect();
GC.WaitForPendingFinalizers();
}));
}
});
}
玩一下这个,点击按钮2开始收集,并注意标签中的数值。打开复选框使其在worker上运行并比较。使用按钮1在之间重置最大值。修改分配代码,你可能想要对位图做些什么,无论你做什么都需要这个hack。
我的观察是:在UI线程上执行收集时延迟约为220毫秒,在worker上运行时延迟约为340毫秒。显然,这根本没有改进。就我所知,你的理论已经不能成立。请自己尝试一下,我只有一个数据点。请注意,在Windows服务器版本或.config文件中使用<gcServer=true>
将会显示非常不同。还有其他东西可以玩玩。
WaitForPendingFinalizers
阻塞我的UI线程。我的假设是错误的吗? - Uwe Keim