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



作为.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);
    }
}

最新更新