繁体   English   中英

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

[英]c# .net why does Task.Run seem to handle Func<T> differently than other code?

作为.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>兼容

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

(当然,要解决问题,只需说出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#语言规范中找到

lambda:

() => 5

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

Action myLittleVariable = () => 5;

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

另一方面,方法组:

M

可以转换为Func<int>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<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();的事实有关int f(); FileStream f(); ,例如从两个不同的基接口的某个接口继承,没有办法解析调用f(); 返回类型不是C#中方法签名的一部分。

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


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

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

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

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

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

当您将Func<TResult>传递给方法Run<TResult>(Func<TResult>)您不必在methodcall上指定泛型,因为它可以推断它。 你的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> ,所以它们会混淆编译器是没有意义的。

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

泰勒詹森的方法对我有用

此外,您可以使用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);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM