Task.Run和预期的委托

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

var f = new Func(uc.ViewModel.SlowProcess); 1) (VALID) string dataPromise = await Task.Run(() => f(token), token); 2) (VALID) string dataPromise = await Task.Run(() => uc.ViewModel.SlowProcess(token), token); 3) (ERROR) string dataPromise = await Task.Run(f(token), token); 

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

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

错误1’System.Threading.Tasks.Task.Run(System.Func>,System.Threading.CancellationToken)’的最佳重载方法匹配有一些无效的参数

错误2参数1:无法从’string’转换为’System.Func>’

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

f(token)作为委托传递实际上就是你在(1)中所做的。

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

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

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


编辑:让我们澄清一下事情。

IL代码可能显示所有。

可能,但我们应该尝试理解代码实际意味着什么。 我们可以使用.NET编译器平台Roslyn来做到这一点:

  1. 在Visual Studio中创建一个新的unit testing项目。
  2. 显示程序包管理器控制台(从View> 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(() => 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. 对于每种情况,我们得到一些共同的输出:

     (...) InvocationExpression | Task.Run(..., token) SimpleMemberAccessExpression | Task.Run IdentifierName | Task GenericName | Run TypeArgumentList |  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 

好的,我们在这里有什么?

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

那是什么意思?

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

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

为什么f(token)不工作呢?

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

没有“参数化代表”这样的东西。 有代理有参数( ActionFunc …)但这与f(token)根本不同,后者是委托f的调用,这导致委托方法的返回值。 这就是为什么f(token)的类型只是string

  • InvocationExpression的类型是被调用方法的返回类型。 代表也是如此。
  • f(token)的类型是string ,因为f已被声明为Func
  • 我们对Task.Run重载仍然需要:
    • Action
    • Func
    • Func
    • Func>
  • 没有比赛。 代码没有编译。

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

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

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

附加信息:

EBNF语法:C#1.0 / 2.0 / 3.0 / 4.0
C#语言规范

您传递给Task.Run与任何预期的签名都不匹配。 它接受一个CancellationToken并返回一个与任何允许的签名都不匹配的string 。 。 摆脱取消令牌允许它匹配这些:

 Run(Func) Run(Func, CancellationToken)