Task.Run和预期的委托

时间:2014-12-05 02:04:24

标签: c# multithreading asynchronous lambda delegates

我不确定如何理解以下观察结果。

var f = new Func<CancellationToken,string>(uc.ViewModel.SlowProcess);

1) (VALID) string dataPromise = await Task.Run<string>(() => f(token), token);

2) (VALID) string dataPromise = await Task.Run<string>(() => uc.ViewModel.SlowProcess(token), token);

3) (ERROR) string dataPromise = await Task.Run<string>(f(token), token);

uc.ViewModel.SlowProcess是一个将CancellationToken作为参数并返回一个字符串的方法。

项目1)和2)有效且工作正常。项目3)无效,给出以下错误:

错误1最佳重载方法匹配&#39; System.Threading.Tasks.Task.Run(System.Func&gt;,System.Threading.CancellationToken)&#39;有一些无效的论点

错误2参数1:无法转换为&#39; string&#39;到&#39; System.Func&gt;&#39;

为什么我不能将f(令牌)作为代理传递?如果我使用不带参数的方法,它也可以。

2 个答案:

答案 0 :(得分:2)

f(token)作为代理传递实际上是您在(1)中所做的事情。

() => f(token)是一个没有参数且返回类型为string的委托。

f(token)不是委托,而是立即调用返回字符串的方法f。这意味着,您的代码不会被任务基础结构调用,但在您创建任务之前,您自己会调用该代码,从而产生一个字符串。您无法从该字符串创建任务,从而导致语法错误。

我会坚持你在(1)中所做的。


编辑:让我们稍微澄清一下。

  

IL代码可能会显示所有内容。

可能,但我们应该尝试理解代码的实际含义。我们可以使用.NET编译器平台Roslyn来完成这个任务:

  1. 在Visual Studio中创建一个新的单元测试项目。
  2. 显示程序包管理器控制台(从View&gt; Other Windows)并输入Install-Package Microsoft.CodeAnalysis -Pre
  3. 创建一个包含以下代码的新类:

    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. 现在,让我们创建一个测试类并分析上面的语句:

    [TestMethod]
    public void Statement_1()
    {
        SyntaxTreeWriter.Write("Task.Run<string>(() => f(token), token)");
    }
    
    [TestMethod]
    public void Statement_2()
    {
        SyntaxTreeWriter.Write("Task.Run<string>(() => uc.ViewModel.SlowProcess(token), token)");
    }
    
    [TestMethod]
    public void Statement_3()
    {
        SyntaxTreeWriter.Write("Task.Run<string>(f(token), token)");
    }
    
  5. 对于每种情况,我们得到一些共同的输出:

    (...)
      InvocationExpression             | Task.Run<string>(..., token)
        SimpleMemberAccessExpression   | Task.Run<string>
          IdentifierName               | Task
          GenericName                  |      Run<string>
            TypeArgumentList           |         <string>
              PredefinedType           |          string
        ArgumentList                   |                 (..., token)
          Argument                     |                  ...
            (...)                      |                  ...
          Argument                     |                       token
            IdentifierName             |                       token
    
  6. 对于(1)和(2),我们得到以下参数:

    ParenthesizedLambdaExpression      | () => ...()
      ParameterList                    | ()
      InvocationExpression             |    => ...()
        (...)                          |       ...
    
  7. 对于(3),我们得到以下参数:

    InvocationExpression               | f(token)
      IdentifierName                   | f
      ArgumentList                     |  (token)
        Argument                       |   token
          IdentifierName               |   token
    
  8. 好的,我们在这里有什么?

    ParenthesizedLambdaExpression显然是一个内联方法声明。此表达式的类型由参数列表(输入), lambda body (输出)的类型和预期类型确定使用lambda的地方(类型推断)。

    这是什么意思?

    • 我们在(1)和(2)中的lambda有一个空的参数列表,因而没有输入。
    • 在两个lambda中,我们调用返回字符串的东西(方法或委托)。
    • 这意味着,我们的lambda表达式的类型将是以下之一:
      • Func<string>
      • Expression<Func<string>>
      • Action
      • Expression<Action>
    • Task.Run方法的第一个参数的类型确定使用哪种类型。鉴于我们使用以CancellationToken作为第二个参数的重载,我们有以下可能性:
      • Action
      • Func<TResult>
      • Func<Task>
      • Func<Task<TResult>>
    • 有两种匹配类型:
      • Func<string>,其中TResult为string
      • Action
    • 第一个具有更高的优先级,因此我们使用重载Task.Run<string>(Func<string>, CancellationToken)

    好。这就是为什么(1)和(2)都起作用的原因:它们使用lambda,它实际上生成了一个委托,并且委托的类型与Task.Run方法的期望相匹配。

    为什么f(token)无效?

      

    一旦你接受传递参数化委托本质上被视为传递它包装的函数,一切都像你期望的那样工作。

    没有&#34;参数化的委托&#34;。有些参数具有参数(Action<T>Func<T,TResult> ...),但这与委托f的调用f(token)根本不同,后者导致委托的返回值方法。这就是为什么f(token)的类型只是string

    • InvocationExpression的类型是被调用方法的返回类型。这同样适用于代表。
    • f(token)的类型为string,因为f已被声明为Func<CancellationToken,string>
    • 我们对Task.Run的重载仍然需要:
      • Action
      • Func<TResult>
      • Func<Task>
      • Func<Task<TResult>>
    • 没有匹配。代码没有编译。

    我们怎么能让它发挥作用?

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

    这可以像TaskExtensions.Run(f, token)一样调用。但我不建议这样做,因为它没有提供额外的价值。

    其他信息:

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

答案 1 :(得分:0)

您传递给Task.Run的代理人与任何预期的签名都不匹配。它需要CancellationToken并返回与allowed signatures.中的任何一个都不匹配的string。摆脱取消令牌可以匹配这些:

Run<TResult>(Func<TResult>)  
Run<TResult>(Func<TResult>, CancellationToken)