作为.NET 4.5一部分的新Task.Run静态方法的行为似乎不像人们预期的那样。
例如:
Task<Int32> t = Task.Run(()=>5);
编译良好,但
Task<Int32> t = Task.Run(MyIntReturningMethod);
...
public Int32 MyIntReturningMethod() {
return (5);
}
抱怨MyIntReturningMethod返回了错误的类型。
也许我只是不明白是哪个超负荷的Task.Run被调用了。但在我看来,上面的lambda代码看起来很像Func<Int32>
,MyIntReturningMethod肯定与Func<Int32>
兼容
你知道发生了什么事吗?Michael
(当然,要解决问题,只需说Task.Run((Func<int>)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<T>(Func<T> f)
{
}
static int M()
{
return 5;
}
}
正如我们所看到的,这与Task
完全没有关系,但仍然会产生同样的问题。
请注意,匿名函数转换和方法组转换(仍然)不是完全相同的东西。有关详细信息,请参阅C#语言规范。
λ:
() => 5
实际上甚至不能转换为CCD_ 6类型。如果你尝试做:
Action myLittleVariable = () => 5;
它将失败,并出现错误CS0201:只有赋值、调用、递增、递减、等待和新对象表达式才能用作语句。因此,对于lambda使用哪个重载是非常清楚的。
另一方面,方法组:
M
可转换为CCD_ 7和CCD_。请记住,完全允许而不是获取返回值,就像以下语句一样:
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<T>(Func<T> f)
{
}
}
在最后一个例子中,lambda实际上可以转换为两种委托类型!(只需尝试删除泛型重载。)对于lambda箭头的右侧,=>
是一个表达式:
int.Parse("5")
其本身作为陈述是有效的。但在这种情况下,过载解决方案仍然可以找到更好的过载。正如我之前所说,检查C#规范
受HansPassant和BlueRaja DannyPflughoeft的启发,这里是最后一个(我认为)例子:
class Program
{
static void Main()
{
Run(M); // won't compile!
}
static void Run(Func<int> f)
{
}
static void Run(Func<FileStream> f)
{
}
static int M()
{
return 5;
}
}
注意,在这种情况下,绝对不可能将int
5
转换为System.IO.FileStream
。方法组转换仍然失败。这可能与以下事实有关:使用两个普通方法int f();
和FileStream f();
,例如由某个接口从两个不同的基本接口继承,无法解析调用f();
。返回类型不是C#中方法签名的一部分。
我仍然避免在我的回答中介绍Task
,因为它可能会给人一种关于这个问题的错误印象。人们很难理解Task
,而且它在BCL中相对较新。
这个答案已经演变了很多。最后,事实证明,这实际上是与线程中相同的潜在问题。为什么Func<T>
与Func<IEnumerable<T>>
不明确?。我的Func<int>
和Func<FileStream>
的例子几乎同样清晰。埃里克·利珀特在另一条线索中给出了一个很好的答案。
这本应在.Net 4.0中修复,但Task.Run()是.Net 4.5 的新版本
.NET 4.5通过添加Task.Run(Func<Task<T>>)
方法具有自己的过载模糊性。以及C#版本5中对async/await的支持。这允许从CCD_ 23到CCD_ 24的隐式转换。
对于async/await来说,这是一个非常好的语法糖,但在这里会产生空洞。方法声明中省略async
关键字是方法重载选择中不考虑的,这打开了另一个痛苦的潘多拉盒子,程序员忘记了在他们想要的时候使用async。否则,遵循通常的C#约定,方法重载选择只考虑方法签名中的方法名称和参数。
需要显式使用委托类型来解决歧义。
当您将Func<TResult>
传递到方法Run<TResult>(Func<TResult>)
时,您不必在方法调用上指定泛型,因为它可以推断它。您的lambda可以进行推断。
然而,您的函数实际上不是Func<TResult>
,而lambda是。
如果你做Func<Int32> f = MyIntReturningMethod
,它是有效的。现在,如果您指定Task.Run<Int32>(MyIntReturningMethod)
,您会期望它也能工作。然而,它不能决定是应该解决Func<Task<TResult>>
过载还是Func<TResult>
过载,这没有多大意义,因为很明显,该方法没有返回任务。
如果您编译如下简单的东西:
void Main()
{
Thing(MyIntReturningMethod);
}
public void Thing<T>(Func<T> 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<System.Int32>..ctor
IL_000E: call UserQuery.Thing
(一些额外的东西来自LINQ Pad的添加……比如UserQuery部分)
IL看起来是一样的,就像你做了一个显式的演员阵容。所以编译器似乎并不知道该使用哪种方法。所以它不知道要自动创建什么样的演员阵容。
您可以使用Task.Run<Int32>((Func<Int32>)MyIntReturningMethod)
来帮助它。尽管我确实同意这似乎是编译器应该能够处理的事情。因为Func<Task<Int32>>
与Func<Int32>
不同,所以它们混淆编译器是没有意义的。
似乎是一个过载解决问题。编译器无法判断您正在调用哪个重载(因为首先它必须找到要创建的正确委托,但它不知道,因为这取决于您正在调用的重载)。它需要猜测和检查,但我猜它没有那么聪明。
Tyler Jensen的方法对我有效。
此外,您可以使用lambda表达式尝试此操作:
public class MyTest
{
public void RunTest()
{
Task<Int32> t = Task.Run<Int32>(() => MyIntReturningMethod());
t.Wait();
Console.WriteLine(t.Result);
}
public int MyIntReturningMethod()
{
return (5);
}
}
这是我的尝试:
public class MyTest
{
public void RunTest()
{
Task<Int32> t = Task.Run<Int32>(new Func<int>(MyIntReturningMethod));
t.Wait();
Console.WriteLine(t.Result);
}
public int MyIntReturningMethod()
{
return (5);
}
}