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
。)
这与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
可以转换为Func
和Action
。 请记住,完全允许不接收返回值,就像语句一样:
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这与线程中的基本问题完全相同为什么Func
与Func
不明确? 。 我的Func
和Func
例子几乎一样清楚。 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
您不必在methodcall上指定generics,因为它可以推断它。 你的lambda做了那个推断。
但是,你的函数实际上不是Func
而lambda是。
如果你做Func
它可以工作。 现在,如果您指定Task.Run
您也希望它也能正常工作。 但是它无法决定是否应该解析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
与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); } }