我不确定如何理解以下观察结果。
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(令牌)作为代理传递?如果我使用不带参数的方法,它也可以。
答案 0 :(得分:2)
将f(token)
作为代理传递实际上是您在(1)中所做的事情。
() => f(token)
是一个没有参数且返回类型为string
的委托。
f(token)
不是委托,而是立即调用返回字符串的方法f
。这意味着,您的代码不会被任务基础结构调用,但在您创建任务之前,您自己会调用该代码,从而产生一个字符串。您无法从该字符串创建任务,从而导致语法错误。
我会坚持你在(1)中所做的。
编辑:让我们稍微澄清一下。
IL代码可能会显示所有内容。
可能,但我们应该尝试理解代码的实际含义。我们可以使用.NET编译器平台Roslyn来完成这个任务:
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<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)");
}
对于每种情况,我们得到一些共同的输出:
(...)
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
对于(1)和(2),我们得到以下参数:
ParenthesizedLambdaExpression | () => ...()
ParameterList | ()
InvocationExpression | => ...()
(...) | ...
对于(3),我们得到以下参数:
InvocationExpression | f(token)
IdentifierName | f
ArgumentList | (token)
Argument | token
IdentifierName | token
ParenthesizedLambdaExpression
显然是一个内联方法声明。此表达式的类型由参数列表(输入), lambda body (输出)的类型和预期类型确定使用lambda的地方(类型推断)。
这是什么意思?
Func<string>
Expression<Func<string>>
Action
Expression<Action>
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
:
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)
一样调用。但我不建议这样做,因为它没有提供额外的价值。
其他信息:
答案 1 :(得分:0)
您传递给Task.Run
的代理人与任何预期的签名都不匹配。它需要CancellationToken
并返回与allowed signatures.中的任何一个都不匹配的string
。摆脱取消令牌可以匹配这些:
Run<TResult>(Func<TResult>)
Run<TResult>(Func<TResult>, CancellationToken)