c# .net why does Task.Run seem to handle Func differently than other code?

前端 未结 6 1437
生来不讨喜
生来不讨喜 2021-01-01 16:07

The new Task.Run static method that\'s part of .NET 4.5 doesn\'t seem to behave as one might expect.

For example:

Task t = Task.Run(()=&         


        
相关标签:
6条回答
  • 2021-01-01 16:25

    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. Your lambda does that inference.

    However, your function is not actually a Func<TResult> whereas the lambda was.

    If you do Func<Int32> f = MyIntReturningMethod it works. Now if you specify Task.Run<Int32>(MyIntReturningMethod) you would expect it to work also. 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.

    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_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)

    The IL looks identical as if you do an explicit cast. 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. 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.

    0 讨论(0)
  • 2021-01-01 16:25

    The approach of Tyler Jensen works for me.

    Also, you can try this using a lambda expression:

    public class MyTest
    {
        public void RunTest()
        {
            Task<Int32> t = Task.Run<Int32>(() => MyIntReturningMethod());
            t.Wait();
            Console.WriteLine(t.Result);
        }
    
        public int MyIntReturningMethod()
        {
            return (5);
        }
    }
    
    0 讨论(0)
  • 2021-01-01 16:31

    (Of course, to get out of the problem, simply say Task.Run((Func<int>)MyIntReturningMethod).)

    This has absolutely nothing to do with Task and so on.

    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.

    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.

    The lambda:

    () => 5
    

    is actually not even convertible to the System.Action type. 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. So it is really clear which overload to use with the lambda.

    On the other hand, the method group:

    M
    

    is convertible to both Func<int> and 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! (Just try to remove the generic overload.) For the right-hand-side of the lambda arrow => is an expression:

    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.


    Inspired by HansPassant and BlueRaja-DannyPflughoeft, here is one final (I think) example:

    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. Still the method group conversion fails. This might be related to the fact the with two ordinary methods int f(); and FileStream f();, for example inherited by some interface from two different base interfaces, there is no way to resolve the call f();. The return type is not part of a method's signature in C#.

    I still avoid to introduce Task in my answer since it could give a wrong impression of what this problem is about. People have a hard time understanding Task, and it is relatively new in the 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>>?. My example with Func<int> and Func<FileStream> is almost as clear. Eric Lippert gives a good answer in that other thread.

    0 讨论(0)
  • 2021-01-01 16:36

    This was supposed to be fixed in .Net 4.0, but Task.Run() is new to .Net 4.5

    .NET 4.5 has its own overload ambiguity by adding the Task.Run(Func<Task<T>>) method. And the support for async/await in C# version 5. Which permits an implicit conversion from T foo() to Func<Task<T>>.

    That's syntax sugar that's pretty sweet for async/await but produces cavities here. 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. Otherwise follows the usual C# convention that only the method name and arguments in the method signature is considered for method overload selection.

    Using the delegate type explicitly is required to resolve the ambiguity.

    0 讨论(0)
  • 2021-01-01 16:40

    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.

    0 讨论(0)
  • 2021-01-01 16:44

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题