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来做到这一点:
- 在Visual Studio中创建一个新的unit testing项目。
- 显示程序包管理器控制台(从View> Other Windows)并输入
Install-Package Microsoft.CodeAnalysis -Pre
-
创建一个包含以下代码的新类:
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--; } }
-
现在,让我们创建一个测试类并分析上面的语句:
[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)"); } -
对于每种情况,我们得到一些共同的输出:
(...) InvocationExpression | Task.Run
(..., token) SimpleMemberAccessExpression | Task.Run IdentifierName | Task GenericName | Run TypeArgumentList | PredefinedType | string ArgumentList | (..., token) Argument | ... (...) | ... Argument | token IdentifierName | token -
对于(1)和(2),我们得到以下参数:
ParenthesizedLambdaExpression | () => ...() ParameterList | () InvocationExpression | => ...() (...) | ...
-
对于(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)
不工作呢?
一旦你接受传递参数化委托本质上被视为传递它包装的函数,一切都像你期望的那样工作。
没有“参数化代表”这样的东西。 有代理有参数( Action
, Func
…)但这与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)