“使用静态”杀死 AsParallel

问题描述

在以下代码中,如果取消注释“using static”行,查询将不会并行运行。为什么?

(Visual Studio 社区 2019,.Net Core 3.1 / .Net 4.8)

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace UsingStatic_Mystery
{
    //using static Enumerable;
    class Program
    {
        static void Main(string[] args)
        {
            var w = new Stopwatch();
            iter:
            w.Start();
            var xx = Enumerable.Range(0,10)
                .AsParallel()
                .OrderByDescending(x => {
                    Thread.Sleep(new Random().Next(100));
                    Console.WriteLine(x);
                    return x;
                }).ToArray();
            w.Stop();
            Console.WriteLine();
            foreach (var x in xx) Console.WriteLine(x);
            Console.WriteLine(w.ElapsedMilliseconds);
            Console.ReadLine();
            w.Reset();
            goto iter;
        }
    }
}

输出,未注释/已注释:

"using static" uncommented

"using static" commented

解决方法

找到:

这是带有 using static 注释的 IL code generated(所以没有 using static):

IL_0038: call class [System.Linq.Parallel]System.Linq.OrderedParallelQuery`1<!!0> [System.Linq.Parallel]System.Linq.ParallelEnumerable::OrderByDescending<int32,int32>(class [System.Linq.Parallel]System.Linq.ParallelQuery`1<!!0>,class [System.Private.CoreLib]System.Func`2<!!0,!!1>)
IL_003d: call !!0[] [System.Linq.Parallel]System.Linq.ParallelEnumerable::ToArray<int32>(class [System.Linq.Parallel]System.Linq.ParallelQuery`1<!!0>)

这是带有 using static 未注释的 IL code generated(所以 带有 using static):

IL_0038: call class [System.Linq]System.Linq.IOrderedEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::OrderByDescending<int32,int32>(class [System.Private.CoreLib]System.Collections.Generic.IEnumerable`1<!!0>,!!1>)
IL_003d: call !!0[] [System.Linq]System.Linq.Enumerable::ToArray<int32>(class [System.Private.CoreLib]System.Collections.Generic.IEnumerable`1<!!0>)

“正确”的一面使用Parallel.OrderBy,“错误”的一面使用Enumerable.OrderBy。由于这个原因,您看到的结果非常清楚。选择一个或另一个 OrderBy 的原因是因为使用 using static Enumerable 您声明 C# 应该更喜欢 Enumerable 类中的方法。

更有趣的是,你有没有像这样编写 using 块:

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;

using static System.Linq.Enumerable;

namespace ConsoleApp1
{

所以在命名空间之外,一切都会“正确”运行(IL code generated)。

I'll say that namespace resolution works by level... 首先,C# 会尝试定义在 using 最内层的所有 namespace,如果没有一个足够好,那么它会上升到 {{1} 的级别}.如果有多个候选人在同一级别,则选择最佳匹配。在没有 namespace 的示例和我给出的示例中,using static + using 都是顶级的,只有一个级别,因此 C# 选择了最佳候选者。在两级using static中检查最里面的一层,而using足以解析using static Enumerable方法,所以没有做额外的检查。

这一次我要再说一遍,SharpLab 是这次响应的 MVP。如果您对 C# 编译器在幕后做了什么有疑问,SharpLab 可以给您答复(从技术上讲,您可以使用 ildasm.exeILSpy,但是 SharpLab 非常直接,因为它是一个网站,您可以交互式更改源代码)。 SVP(第二个有价值的球员)(对我来说)是 WinMerge,我用来比较 IL 程序集?

回复评论

C# 6.0 draft reference 页面说

using_namespace_directive 引用的 namespace_name 的解析方式与 using_alias_directive 引用的 namespace_or_type_name 的解析方式相同。因此,同一编译单元或命名空间体中的 using_namespace_directives 互不影响,可以按任意顺序编写。

然后

在使用命名空间指令中讨论了多个 using_namespace_directives 和 using_static_directives 之间的歧义。

所以第一条规则甚至适用于OrderBy。这就解释了为什么第三个例子(我的)等价于 no-using static

关于为什么 using staticParallelEnumerable.OrderedBy() 都被 C# 编译器检查时更好,很简单:

Enumerable.OrderBy() 返回一个 AsParallel()(实现 ParallelQuery<TSource>

IEnumerable<TSource> 签名:

ParallelEnumerable.OrderedBy()

public static OrderedParallelQuery<TSource> OrderBy<TSource,TKey>(this ParallelQuery<TSource> source,Func<TSource,TKey> keySelector) 签名:

Enumerable.OrderedBy()

第一个接受 public static IOrderedEnumerable<TSource> OrderBy<TSource,TKey>(this IEnumerable<TSource> source,TKey> keySelector) ,它与 ParallelQuery<TSource> 返回的类型完全相同,不需要“向下转换”。