c#.net为什么Task.Run似乎以不同于其他代码的方式处理Func ?

作为.NET 4.5的一部分的新Task.Run静态方法似乎没有像人们期望的那样运行。

例如:

Task t = Task.Run(()=>5); 

编译好,但是

 Task t = Task.Run(MyIntReturningMethod); ... public Int32 MyIntReturningMethod() { return (5); } 

抱怨MyIntReturningMethod返回了错误的类型。

也许我只是不了解正在调用Task.Run的哪个重载。 但在我看来,上面的lambda代码看起来很像Func ,而MyIntReturningMethod肯定与Func兼容

对于发生了什么的任何想法? 迈克尔

(当然,要解决问题,只需说出Task.Run((Func)MyIntReturningMethod) 。)

这与Task等完全无关。

这里需要注意的一个问题是,当存在很多重载时,编译器错误文本将只关注一对“重载”。 所以这很令人困惑。 原因是确定最佳过载的算法考虑了所有重载,并且当该算法断定没有找到最佳过载时,不会产生错误文本的某对重载,因为所有重载都可能(或可能不)参与过。

要了解会发生什么,请参阅此简化版本:

 static class Program { static void Main() { Run(() => 5); // compiles, goes to generic overload Run(M); // won't compile! } static void Run(Action a) { } static void Run(Func f) { } static int M() { return 5; } } 

正如我们所看到的,这绝对没有对Task引用,但仍会产生同样的问题。

请注意,匿名函数转换和方法组转换(仍然)不完全相同。 详细信息可在C#语言规范中找到

lambda:

 () => 5 

实际上甚至不能转换为System.Action类型。 如果您尝试这样做:

 Action myLittleVariable = () => 5; 

它将失败并出现错误CS0201:只能将赋值,调用,递增,递减,等待和新对象表达式用作语句 。 因此很清楚与lambda一起使用哪个重载。

另一方面,方法组:

 M 

可以转换为FuncAction 。 请记住,完全允许接收返回值,就像语句一样:

 M(); // don't use return value 

本身是有效的。

这种方式回答了这个问题,但我会举一个额外的例子来说明一点。 考虑这个例子:

 static class Program { static void Main() { Run(() => int.Parse("5")); // compiles! } static void Run(Action a) { } static void Run(Func f) { } } 

在最后一个示例中,lambda实际上可以转换为两种委托类型! (只是尝试删除generics重载。)对于lambda的右侧,箭头=>是一个表达式:

 int.Parse("5") 

这本身就是一个有效的声明。 但在这种情况下,重载分辨率仍然可以找到更好的过载。 正如我之前所说,检查C#规范。


受HansPassant和BlueRaja-DannyPflughoeft的启发,这是最后一个(我认为)的例子:

 class Program { static void Main() { Run(M); // won't compile! } static void Run(Func f) { } static void Run(Func f) { } static int M() { return 5; } } 

请注意,在这种情况下,绝对没有办法将int 5转换为System.IO.FileStream 。 方法组转换仍然失败。 这可能与两个普通方法int f();的事实有关int f();FileStream f(); ,例如从两个不同的基接口的某个接口inheritance,没有办法解析调用f(); 。 返回类型不是C#中方法签名的一部分。

我仍然避免在我的回答中介绍Task ,因为它可能会给出这个问题的错误印象。 人们很难理解Task ,而且它在BCL中相对较新。


这个答案已经发展了很多。 最后,事实certificate这与线程中的基本问题完全相同为什么FuncFunc>不明确? 。 我的FuncFunc例子几乎一样清楚。 Eric Lippert在其他post中给出了一个很好的答案。

这应该在.Net 4.0中修复,但Task.Run()是.Net 4.5的新function

通过添加Task.Run(Func>)方法,.NET 4.5有自己的重载歧义。 并且在C#版本5中支持async / await。它允许从T foo()Func>的隐式转换。

这是async / await非常甜的语法糖,但在这里会产生空洞。 方法声明中的async关键字的省略不会在方法重载选择中被考虑,这会打开另一个痛苦的盒子,程序员忘记在他们意图使用异步时。 否则遵循通常的C#约定,只考虑方法签名中的方法名称和参数进行方法重载选择。

需要显式使用委托类型来解决歧义。

当您将Func传递给方法Run(Func)您不必在methodcall上指定generics,因为它可以推断它。 你的lambda做了那个推断。

但是,你的函数实际上不是Func而lambda是。

如果你做Func f = MyIntReturningMethod它可以工作。 现在,如果您指定Task.Run(MyIntReturningMethod)您也希望它也能正常工作。 但是它无法决定是否应该解析Func>重载或Func重载,这没有多大意义,因为很明显该方法没有返回任务。

如果你编译简单如下的东西:

 void Main() { Thing(MyIntReturningMethod); } public void Thing(Func o) { o(); } public Int32 MyIntReturningMethod() { return (5); } 

IL看起来像这样….

 IL_0001: ldarg.0 IL_0002: ldarg.0 IL_0003: ldftn UserQuery.MyIntReturningMethod IL_0009: newobj System.Func..ctor IL_000E: call UserQuery.Thing 

(一些额外的东西来自LINQ Pad的补充……就像UserQuery部分一样)

IL看起来就像你做一个明确的演员一样。 所以看起来编译器实际上并不知道使用哪种方法。 所以它不知道自动创建什么强制转换。

您可以使用Task.Run((Func)MyIntReturningMethod)来帮助它。 虽然我同意这似乎是编译器应该能够处理的东西。 因为Func>Func ,所以它们会混淆编译器是没有意义的。

似乎是一个重载解决问题。 编译器无法分辨您正在调用哪个重载(因为首先它必须找到要创建的正确委托,它不知道因为这取决于您正在调用的重载)。 它必须猜测和检查,但我猜它不是那么聪明。

Tyler Jensen的方法对我有用 。

此外,您可以使用lambda表达式尝试此操作:

 public class MyTest { public void RunTest() { Task t = Task.Run(() => MyIntReturningMethod()); t.Wait(); Console.WriteLine(t.Result); } public int MyIntReturningMethod() { return (5); } } 

这是我的抨击:

 public class MyTest { public void RunTest() { Task t = Task.Run(new Func(MyIntReturningMethod)); t.Wait(); Console.WriteLine(t.Result); } public int MyIntReturningMethod() { return (5); } }