简体   繁体   English

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

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

The new Task.Run static method that's part of .NET 4.5 doesn't seem to behave as one might expect. 作为.NET 4.5一部分的新Task.Run静态方法似乎没有像人们期望的那样运行。

For example: 例如:

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

compiles fine, but 编译好,但是

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

complains that MyIntReturningMethod is returning the wrong type. 抱怨MyIntReturningMethod返回了错误的类型。

Perhaps I am just not understanding which overload of Task.Run is being called. 也许我只是不了解正在调用Task.Run的哪个重载。 But in my mind, my lambda code above looks a lot like a Func<Int32> , and MyIntReturningMethod is definitely compatible with Func<Int32> 但在我看来,上面的lambda代码看起来很像Func<Int32> ,而MyIntReturningMethod肯定与Func<Int32>兼容

Any ideas of what's going on? 对于发生了什么的任何想法? Michael 迈克尔

(Of course, to get out of the problem, simply say Task.Run((Func<int>)MyIntReturningMethod) .) (当然,要解决问题,只需说出Task.Run((Func<int>)MyIntReturningMethod) 。)

This has absolutely nothing to do with Task and so on. 这与Task等完全无关。

One problem to be aware of here is that when very many overloads are present, the compiler error text will focus on just one "pair" of overloads. 这里需要注意的一个问题是,当存在非常多的重载时,编译器错误文本将仅关注一对“重载”。 So that is confusing. 所以这很令人困惑。 The reason is that the algorithm to determine the best overload considers all overloads, and when that algorithm concludes that no best overload can be found, that does not produce a certain pair of overloads for the error text because all overloads may (or may not) have been involved. 原因是确定最佳过载的算法考虑了所有重载,并且当该算法断定没有找到最佳过载时,不会产生错误文本的某对重载,因为所有重载都可能(或可能不)参与过。

To understand what happens, see instead this simplified version: 要了解会发生什么,请参阅此简化版本:

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;
    }
}

As we see, this has absolutely no reference to Task , but still produces the same problem. 正如我们所看到的,这绝对没有对Task引用,但仍会产生同样的问题。

Note that anonymous function conversions and method group conversions are (still) not the exact same thing. 请注意,匿名函数转换和方法组转换(仍)不完全相同。 Details are to be found in the C# Language Specification . 详细信息可在C#语言规范中找到

The lambda: lambda:

() => 5

is actually not even convertible to the System.Action type. 实际上甚至不能转换为System.Action类型。 If you try to do: 如果您尝试这样做:

Action myLittleVariable = () => 5;

it will fail with error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement . 它将失败并出现错误CS0201:只能将赋值,调用,递增,递减,等待和新对象表达式用作语句 So it is really clear which overload to use with the lambda. 因此很清楚与lambda一起使用哪个重载。

On the other hand, the method group: 另一方面,方法组:

M

is convertible to both Func<int> and Action . 可以转换为Func<int>Action Remember that it is perfectly allowed to not pick up a return value, just like the statement: 请记住,完全允许接收返回值,就像语句一样:

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

is valid by itself. 本身是有效的。

This sort-of answers the question but I will give an extra example to make an additional point. 这种方式回答了这个问题,但我会举一个额外的例子来说明一个问题。 Consider the example: 考虑这个例子:

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

    static void Run(Action a)
    {
    }
    static void Run<T>(Func<T> f)
    {
    }
}

In this last example, the lambda is actually convertible to both delegate types! 在最后一个示例中,lambda实际上可以转换为两种委托类型! (Just try to remove the generic overload.) For the right-hand-side of the lambda arrow => is an expression: (只是尝试删除泛型重载。)对于lambda的右侧,箭头=>是一个表达式:

int.Parse("5")

which is valid as a statement by itself. 这本身就是一个有效的声明。 But overload resolution can still find a better overload in this case. 但是在这种情况下,重载分辨率仍然可以找到更好的过载。 As I said earlier, check the C# Spec. 正如我之前所说,检查C#规范。


Inspired by HansPassant and BlueRaja-DannyPflughoeft, here is one final (I think) example: 受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;
    }
}

Note that in this case, there is absolutely no way the int 5 could be converted into a System.IO.FileStream . 请注意,在这种情况下,绝对没有办法将int 5转换为System.IO.FileStream Still the method group conversion fails. 方法组转换仍然失败。 This might be related to the fact the with two ordinary methods int f(); 这可能与两个普通方法int f();的事实有关int f(); and FileStream f(); FileStream f(); , for example inherited by some interface from two different base interfaces, there is no way to resolve the call f(); ,例如从两个不同的基接口的某个接口继承,没有办法解析调用f(); . The return type is not part of a method's signature in C#. 返回类型不是C#中方法签名的一部分。

I still avoid to introduce Task in my answer since it could give a wrong impression of what this problem is about. 我仍然避免在我的回答中介绍Task ,因为它可能会给出这个问题的错误印象。 People have a hard time understanding Task , and it is relatively new in the BCL. 人们很难理解Task ,而且它在BCL中相对较新。


This answer has evolved a lot. 这个答案已经发展了很多。 In the end, it turns out that this is really the same underlying problem as in the thread Why is Func<T> ambiguous with Func<IEnumerable<T>> ? 最后,事实证明这与线程中的基本问题完全相同为什么Func<T>Func<IEnumerable<T>>不明确? . My example with Func<int> and Func<FileStream> is almost as clear. 我的Func<int>Func<FileStream>例子几乎一样清楚。 Eric Lippert gives a good answer in that other thread. Eric Lippert在其他帖子中给出了一个很好的答案。

This was supposed to be fixed in .Net 4.0, but Task.Run() is new to .Net 4.5 这应该在.Net 4.0中修复,但Task.Run()是.Net 4.5的新功能

.NET 4.5 has its own overload ambiguity by adding the Task.Run(Func<Task<T>>) method. 通过添加Task.Run(Func<Task<T>>)方法,.NET 4.5有自己的重载歧义。 And the support for async/await in C# version 5. Which permits an implicit conversion from T foo() to Func<Task<T>> . 在C#版本5中支持async / await。它允许从T foo()Func<Task<T>>的隐式转换。

That's syntax sugar that's pretty sweet for async/await but produces cavities here. 这是async / await非常甜的语法糖,但在这里会产生空洞。 The omission of the async keyword on the method declaration is not considered in the method overload selection, that opens another pandora box of misery with programmers forgetting to use async when they meant to. 方法声明中的async关键字的省略不会在方法重载选择中被考虑,这会打开另一个苦差事框,程序员忘记在他们意图使用异步时。 Otherwise follows the usual C# convention that only the method name and arguments in the method signature is considered for method overload selection. 否则遵循通常的C#约定,只考虑方法签名中的方法名称和参数进行方法重载选择。

Using the delegate type explicitly is required to resolve the ambiguity. 需要显式使用委托类型来解决歧义。

When you pass a Func<TResult> into a method Run<TResult>(Func<TResult>) you don't have to specify the generic on the methodcall because it can infer it. 当您将Func<TResult>传递给方法Run<TResult>(Func<TResult>)您不必在methodcall上指定泛型,因为它可以推断它。 Your lambda does that inference. 你的lambda做了那个推断。

However, your function is not actually a Func<TResult> whereas the lambda was. 但是,你的函数实际上不是Func<TResult>而lambda是。

If you do Func<Int32> f = MyIntReturningMethod it works. 如果你做Func<Int32> f = MyIntReturningMethod它可以工作。 Now if you specify Task.Run<Int32>(MyIntReturningMethod) you would expect it to work also. 现在,如果您指定Task.Run<Int32>(MyIntReturningMethod)您也希望它也能正常工作。 However it can't decide if it should resolve the Func<Task<TResult>> overload or the Func<TResult> overload, and that doesn't make much sense because its obvious that the method is not returning a task. 但是它无法确定它是否应该解析Func<Task<TResult>>重载或Func<TResult>重载,这没有多大意义,因为很明显该方法没有返回任务。

If you compile something simple like follows: 如果你编译简单如下的东西:

void Main()
{
    Thing(MyIntReturningMethod);
}


public void Thing<T>(Func<T> o)
{
    o();
}

public Int32 MyIntReturningMethod()
{
return (5);
}

the IL looks like this.... 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

(Some of the extra stuff is from LINQ Pad's additions... like the UserQuery part) (一些额外的东西来自LINQ Pad的补充......就像UserQuery部分一样)

The IL looks identical as if you do an explicit cast. IL看起来像是一个明确的演员表。 So it seems like the compiler does't actually know which method to use. 所以看起来编译器实际上并不知道使用哪种方法。 So it doesn't know what cast to create automatically. 所以它不知道自动创建什么强制转换。

You can just use Task.Run<Int32>((Func<Int32>)MyIntReturningMethod) to help it out a bit. 您可以使用Task.Run<Int32>((Func<Int32>)MyIntReturningMethod)来帮助它。 Though I do agree that this seems like something the compiler should be able to handle. 虽然我同意这似乎是编译器应该能够处理的东西。 Because Func<Task<Int32>> is not the same as Func<Int32> , so it doesn't make sense that they would confuse the compiler. 因为Func<Task<Int32>>Func<Int32> ,所以它们会混淆编译器是没有意义的。

Seems like an overload resolution problem. 似乎是一个重载解决问题。 The compiler can't tell which overload you're calling (because first it has to find the correct delegate to create, which it doesn't know because that depends on the overload you're calling). 编译器无法判断您正在调用哪个重载(因为首先它必须找到要创建的正确委托,它不知道因为这取决于您正在调用的重载)。 It would have to guess-and-check but I'm guessing it's not that smart. 它必须猜测和检查,但我猜它不是那么聪明。

The approach of Tyler Jensen works for me. 泰勒詹森的方法对我有用

Also, you can try this using a lambda expression: 此外,您可以使用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);
    }
}

Here's my stab at it: 这是我的抨击:

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