问题描述
我不确定如何理解以下观察到的结果.
I am not sure how to make sense out of the following observed results.
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 作为参数并返回字符串的方法.
uc.ViewModel.SlowProcess is a method that takes a CancellationToken as a parameter and returns a string.
第 1) 和 2) 项有效且工作正常.第 3) 项无效,出现以下错误:
Item 1) and 2) are valid and work correctly. Item 3) is invalid, giving the follow errors:
错误 1 'System.Threading.Tasks.Task.Run(System.Func>, System.Threading.CancellationToken)' 的最佳重载方法匹配有一些无效参数
Error 1 The best overloaded method match for 'System.Threading.Tasks.Task.Run(System.Func>, System.Threading.CancellationToken)' has some invalid arguments
错误 2 参数 1:无法从 'string' 转换为 'System.Func>'
Error 2 Argument 1: cannot convert from 'string' to 'System.Func>'
为什么我不能将 f(token) 作为委托传递?如果我使用不带参数的方法,它也可以工作.
Why can't I pass f(token) as a delegate? If I do it with a method that takes no parameters, it also works.
推荐答案
将 f(token)
作为委托传递实际上是您在 (1) 中所做的.
Passing f(token)
as a delegate is actually what you're doing in (1).
<代码>() =>f(token) 是一个没有参数并且返回类型 string
的委托.
() => f(token)
is a delegate with no arguments and return type string
.
f(token)
不是委托,而是立即调用返回字符串的方法 f
.这意味着,您的代码不是由 Task 基础结构调用,而是由您自己调用,甚至在创建 Task 之前,都会生成一个字符串.您无法从该字符串创建任务,这会导致语法错误.
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.
我会坚持你在 (1) 中所做的.
I would stick with what you did in (1).
让我们澄清一下.
IL 代码可能会显示所有内容.
IL code probably shows all.
可能,但我们宁愿尝试理解代码的实际含义.我们可以使用 .NET 编译器平台 Roslyn 来做到这一点:
Probably, but we should rather try to understand what the code actually means. We can do this using Roslyn, the .NET Compiler Platform:
- 在 Visual Studio 中创建一个新的单元测试项目.
- 显示包管理器控制台(从视图 > 其他窗口)并输入
Install-Package Microsoft.CodeAnalysis -Pre
创建一个包含以下代码的新类:
- Create a new Unit Test Project in Visual Studio.
- Show the Package Manager Console (from View > Other Windows) and enter
Install-Package Microsoft.CodeAnalysis -Pre
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--;
}
}
现在,让我们创建一个测试类并从上面分析您的语句:
Now, let's create a Test Class and analyze your statements from above:
[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)");
}
对于每种情况,我们都会得到一些共同的输出:
For each case, we get some common output:
(...)
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),我们得到以下参数:
For (1) and (2), we get the following argument:
ParenthesizedLambdaExpression | () => ...()
ParameterList | ()
InvocationExpression | => ...()
(...) | ...
对于 (3),我们得到以下参数:
For (3) instead, we get the following argument:
InvocationExpression | f(token)
IdentifierName | f
ArgumentList | (token)
Argument | token
IdentifierName | token
好的,我们这里有什么?
ParenthesizedLambdaExpression
显然是一个内联方法声明.此表达式的类型由参数列表(输入)、lambda 主体(输出)的类型和预期类型确定使用 lambda 的位置(类型推断).
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).
这是什么意思?
- 我们在 (1) 和 (2) 中的 lambdas 有一个空参数列表,因此没有输入.
- 在两个 lambdas 中,我们调用一些返回字符串的东西(方法或委托).
- 这意味着,我们的 lambda 表达式的类型将是以下之一:
函数<字符串>
表达式<Func<string>>
动作
表达式<动作>
动作
Func
Func<任务>
Func<Task<TResult>>
Func
,其中 TResult 为string
动作
Func<string>
, where TResult isstring
Action
好的.这就是 (1) 和 (2) 都起作用的原因:它们使用 lambda,它实际上生成了一个委托,并且委托的类型与
Task.Run
方法的预期相匹配.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.那为什么
f(token)
不起作用?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.
没有参数化委托"之类的东西.有些委托有参数(
Action<T>
,Func
...),但这与f(token)
,这是对委托 f 的调用,这会导致委托方法的返回值.这就是为什么f(token)
的类型只是string
:There is no such thing as a "parameterized delegate". There are delegates that have parameters (
Action<T>
,Func<T,TResult>
...) but this is fundamentally different fromf(token)
which is an invocation of delegate f, which results in the return value of the delegated method. That's why the type off(token)
simply isstring
:- InvocationExpression 的类型是被调用方法的返回类型.这同样适用于代表.
f(token)
的类型是string
,因为f
已经被声明为Func
代码>. - 我们的
Task.Run
重载仍然需要:动作
Func
Func<任务>
Func<Task<TResult>>
- The type of an InvocationExpression is the return type of the called method. The same applies to delegates.
- The type of
f(token)
isstring
, becausef
has been declared asFunc<CancellationToken,string>
. - Our overloads for
Task.Run
still take:Action
Func<TResult>
Func<Task>
Func<Task<TResult>>
我们怎样才能让它发挥作用?
How could we make it work?
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)
一样调用.但我不建议这样做,因为它没有任何附加价值.This could be called like
TaskExtensions.Run(f, token)
. But I would not recommend doing that, as it provides no additional value whatsoever.附加信息:
EBNF 语法:C# 1.0/2.0/3.0/4.0
C# 语言规范这篇关于Task.Run 和预期的委托的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!