Task.Run and expected delegate

前端 未结 2 718
甜味超标
甜味超标 2021-01-16 08:06

I am not sure how to make sense out of the following observed results.

var f = new Func(uc.ViewModel.SlowProcess);

1) (VALID         


        
2条回答
  •  醉话见心
    2021-01-16 09:06

    Passing f(token) as a delegate is actually what you're doing in (1).

    () => f(token) is a delegate with no arguments and return type string.

    f(token) is not a delegate, but an immediate invocation of method f that returns a string. That means, your code isn't called by the Task infrastructure, but by yourself, before the Task is even created, resulting in a string. You can't create a Task from that string, which leads to the syntax error.

    I would stick with what you did in (1).


    Edit: Let's clarify things a bit.

    IL code probably shows all.

    Probably, but we should rather try to understand what the code actually means. We can do this using Roslyn, the .NET Compiler Platform:

    1. Create a new Unit Test Project in Visual Studio.
    2. Show the Package Manager Console (from View > Other Windows) and enter Install-Package Microsoft.CodeAnalysis -Pre
    3. Create a new class containing the following code:

      using System;
      using Microsoft.CodeAnalysis;
      using Microsoft.CodeAnalysis.CSharp;
      
      public class SyntaxTreeWriter : CSharpSyntaxWalker
      {
          public static void Write(string code)
          {
              var options = new CSharpParseOptions(kind: SourceCodeKind.Script);
              var syntaxTree = CSharpSyntaxTree.ParseText(code, options);
              new SyntaxTreeWriter().Visit(syntaxTree.GetRoot());
          }
      
          private static int Indent = 0;
          public override void Visit(SyntaxNode node)
          {
              Indent++;
              var indents = new String(' ', Indent * 2);
              Console.WriteLine(indents + node.CSharpKind());
              base.Visit(node);
              Indent--;
          }
      }
      
    4. Now, let's create a Test Class and analyze your statements from above:

      [TestMethod]
      public void Statement_1()
      {
          SyntaxTreeWriter.Write("Task.Run(() => f(token), token)");
      }
      
      [TestMethod]
      public void Statement_2()
      {
          SyntaxTreeWriter.Write("Task.Run(() => uc.ViewModel.SlowProcess(token), token)");
      }
      
      [TestMethod]
      public void Statement_3()
      {
          SyntaxTreeWriter.Write("Task.Run(f(token), token)");
      }
      
    5. For each case, we get some common output:

      (...)
        InvocationExpression             | Task.Run(..., token)
          SimpleMemberAccessExpression   | Task.Run
            IdentifierName               | Task
            GenericName                  |      Run
              TypeArgumentList           |         
                PredefinedType           |          string
          ArgumentList                   |                 (..., token)
            Argument                     |                  ...
              (...)                      |                  ...
            Argument                     |                       token
              IdentifierName             |                       token
      
    6. For (1) and (2), we get the following argument:

      ParenthesizedLambdaExpression      | () => ...()
        ParameterList                    | ()
        InvocationExpression             |    => ...()
          (...)                          |       ...
      
    7. For (3) instead, we get the following argument:

      InvocationExpression               | f(token)
        IdentifierName                   | f
        ArgumentList                     |  (token)
          Argument                       |   token
            IdentifierName               |   token
      

    Ok, what do we have here?

    A ParenthesizedLambdaExpression obviously is an inline method declaration. The type of this expression is determined by the parameter list (input), the type of the lambda body (output) and by the expected type where the lambda is used (type inference).

    What does that mean?

    • Our lambdas in (1) and (2) have an empty parameter list and thereby no input.
    • In both lambdas, we invoke something (method or delegate) that returns a string.
    • That means, the type of our lambda expression will be one of the following:
      • Func
      • Expression>
      • Action
      • Expression
    • The type of the first argument of our Task.Run method determines which type is used. We have the following possibilities, given that we use an overload that takes a CancellationToken as second parameter:
      • Action
      • Func
      • Func
      • Func>
    • There are two matching types:
      • Func, where TResult is string
      • Action
    • The first one has higher precedence, so we use the overload Task.Run(Func, CancellationToken)

    Okay. That's why (1) and (2) both work: They use a lambda, which in fact generates a delegate, and the type of the delegate matches the expectations of the Task.Run method.

    Why is f(token) not working then?

    Once you accept that passing a parameterized delegate essentially gets treated like passing the function(s) it wraps, everything works like you would expect.

    There is no such thing as a "parameterized delegate". There are delegates that have parameters (Action, Func...) but this is fundamentally different from f(token) which is an invocation of delegate f, which results in the return value of the delegated method. That's why the type of f(token) simply is string:

    • The type of an InvocationExpression is the return type of the called method. The same applies to delegates.
    • The type of f(token) is string, because f has been declared as Func.
    • Our overloads for Task.Run still take:
      • Action
      • Func
      • Func
      • Func>
    • There is no match. Code's not compiling.

    How could we make it work?

    public static class TaskExtensions
    {
        public static Task Run(Func function, CancellationToken token)
        {
            Func wrappedFunction = () => function(token);
            return Task.Run(wrappedFunction, token);
        }
    }
    

    This could be called like TaskExtensions.Run(f, token). But I would not recommend doing that, as it provides no additional value whatsoever.

    Additional information:

    EBNF Syntax: C# 1.0/2.0/3.0/4.0
    C# Language Specification

提交回复
热议问题