我发现托管vs .Net本机代码有一个奇怪的区别.我有一个繁重的工作重定向到线程池.当在托管代码中运行应用程序时,一切都运行顺畅,但是一旦我打开本机编译 - 任务运行速度慢几十倍,以至于它挂起UI线程(我猜CPU是如此过载).
以下是调试输出的两个屏幕截图,左侧的屏幕截图来自托管代码,右侧的屏幕截图来自本机编译.正如您所看到的,UI任务所消耗的时间在两种情况下几乎相同,直到启动线程池作业时 - 然后在托管版本中UI经过的时间增长(实际上UI被阻止,您无法采取任何操作).线程池工作的时间不言自明.
重现问题的示例代码:
private int max = 2000;
private async void UIJob_Click(object sender, RoutedEventArgs e)
{
IProgress progress = new Progress((p) => { MyProgressBar.Value = (double)p / max; });
await Task.Run(async () => { await SomeUIJob(progress); });
}
private async Task SomeUIJob(IProgress progress)
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < max; i++)
{
if (i % 100 == 0) { Debug.WriteLine($" UI time elapsed => {watch.ElapsedMilliseconds}"); watch.Restart(); }
await Task.Delay(1);
progress.Report(i);
}
}
private async void ThreadpoolJob_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Firing on Threadpool");
await Task.Run(() =>
{
double a = 0.314;
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 50000000; i++)
{
a = Math.Sqrt(a) + Math.Sqrt(a + 1) + i;
if (i % 10000000 == 0) { Debug.WriteLine($"Threadpool -> a value = {a} got in {watch.ElapsedMilliseconds} ms"); watch.Restart(); };
}
});
Debug.WriteLine("Finished with Threadpool");
}
如果您需要完整的样本 - 那么您可以在此处下载.
正如我所测试的那样,在调试和发布版本中,优化/非优化代码都会出现差异.
有没有人知道什么会导致这个问题?
导致此问题的原因是"ThreadPool"数学循环导致GC饥饿.从本质上讲,GC已经决定它需要运行(由于想要进行一些互操作分配)并且它试图阻止所有线程进行收集/压缩.不幸的是,我们还没有添加.NET Native能够劫持热循环的能力,就像你下面的那样.有关将.NET应用商店应用迁移到.NET Native页面的简要说明如下:
无需在任何线程上进行调用(例如,while(true);)的无限循环可能会使应用程序停止.同样,大量或无限等待可能会使应用程序停止运行.
解决此问题的一种方法是在循环中添加一个调用站点(GC非常乐意在尝试调用另一个方法时中断您的线程!).
for (long i = 0; i < 5000000000; i++) { MaybeGCMeHere(); // new callsite a = Math.Sqrt(a) + Math.Sqrt(a + 1) + i; if (i % 1000000000 == 0) { Debug.WriteLine($"Threadpool -> a value = {a} got in {watch.ElapsedMilliseconds} ms"); watch.Restart(); }; } ... [MethodImpl(MethodImplOptions.NoInlining)] // need this so the callsite isn’t optimized away private void MaybeGCMeHere() { }
缺点是你会有这种"丑陋"的黑客攻击,你可能会受到添加指令的影响.我让这里的一些人知道,我们认为这件事"非常罕见"实际上是由客户打击,我们会看到可以做些什么.
谢谢你的报道!
更新:我们围绕这种情况做了一些重大改进,并且能够劫持大多数长期运行的GC线程.这些修复程序可能会在4月份的Update 2 UWP工具集中提供吗?(我不控制出货时间表:-))
更新更新:新工具现已作为UWP工具1.3.1的一部分提供.我们不希望有一个完美的解决方案来积极地反对被GC劫持的线程,但我希望使用最新工具可以更好地利用这种情况.让我们知道!