c# - 在 .Net native 中的线程池上运行的异步任务性能非常差

标签 c# async-await win-universal-app uwp .net-native

我观察到托管代码与 .Net native 代码之间存在奇怪的差异。我有一个繁重的工作重定向到线程池。在托管代码中运行应用程序时,一切都运行顺利,但只要我打开 native 编译 - 任务运行速度就会慢几倍,而且速度太慢以至于挂起 UI 线程(我猜 CPU 太过载了)。

这里有两张调试输出的截图,左边一张来自托管代码,右边一张来自 native 编译。如您所见,两种情况下 UI 任务消耗的时间几乎相同,直到线程池作业启动的时间 - 然后在托管版本中 UI 运行时间增长(实际上 UI 被阻塞,您无法采取任何操作)。线程池作业的时间不言而喻。

Managed Native

重现问题的示例代码:

private int max = 2000;
private async void UIJob_Click(object sender, RoutedEventArgs e)
{
    IProgress<int> progress = new Progress<int>((p) => { MyProgressBar.Value = (double)p / max; });
    await Task.Run(async () => { await SomeUIJob(progress); });
}

private async Task SomeUIJob(IProgress<int> 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");
}

如果您需要完整的样本 - 那么您可以 download it here .

正如我所测试的,差异出现在优化/非优化代码上,无论是调试版本还是发布版本。

有没有人知道是什么导致了这个问题?

最佳答案

此问题是由于“ThreadPool”数学循环导致 GC 饥饿造成的。本质上,GC 已经决定它需要运行(因为想要进行一些互操作分配)并且它试图停止所有线程来进行收集/压缩。不幸的是,我们还没有为 .NET Native 添加劫持热循环的功能,如下所示。这在 Migrating Your Windows Store App to .NET Native 上有简要提及。 页面为:

Infinite looping without making a call (for example, while(true);) on any thread may bring the app to a halt. Similarly, large or infinite waits may bring the app to a halt.

解决此问题的一种方法是在您的循环中添加一个调用站点(当 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 月份发布的 UWP 工具的 Update 2 集中可用? (我无法控制发货时间表:-))

更新更新:新工具现在作为 UWP 工具 1.3.1 的一部分提供。我们不希望有一个完美的解决方案来解决线程积极对抗被 GC 劫持的问题,但我希望使用最新的工具可以改善这种情况。让我们知道!

关于c# - 在 .Net native 中的线程池上运行的异步任务性能非常差,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34666159/

相关文章:

c# - gridview 中的链式下拉列表

c# - 日期格式问题,String 未被识别为有效的 DateTime

c# - 出错时重试异步文件上传

python - 什么是 Python 中的异步?

C# 异步/等待 I/O 绑定(bind)与 CPU 绑定(bind)操作

windows - 如何传递具有 "Windows App Certification Kit - Test"的 UWP 应用程序的 "Restricted namespace"?

c# - 林克 : Select multiple values and assign to select with specific option/value

c# - 无法隐式转换类型 system.data.entity.infrastruct.DbRawSqlQuery<> 。存在显式转换

c# - UWP 中的组合框 SelectedItem 和绑定(bind)

c# - 如何在windows universal app中创建app bar slide menu