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 is a method that takes a CancellationToken as a parameter and returns a string.
Item 1) and 2) are valid and work correctly. Item 3) is invalid, giving the follow errors:
Error 1 The best overloaded method match for 'System.Threading.Tasks.Task.Run(System.Func>, System.Threading.CancellationToken)' has some invalid arguments
Error 2 Argument 1: cannot convert from 'string' to 'System.Func>'
Why can't I pass f(token) as a delegate? If I do it with a method that takes no parameters, it also works.
Passing
f(token)
as a delegate is actually what you're doing in (1).() => f(token)
is a delegate with no arguments and return typestring
.f(token)
is not a delegate, but an immediate invocation of methodf
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.I would stick with what you did in (1).
Edit: Let's clarify things a bit.
Probably, but we should rather try to understand what the code actually means. We can do this using Roslyn, the .NET Compiler Platform:
Install-Package Microsoft.CodeAnalysis -Pre
Create a new class containing the following code:
Now, let's create a Test Class and analyze your statements from above:
For each case, we get some common output:
For (1) and (2), we get the following argument:
For (3) instead, we get the following argument:
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).What does that mean?
Func<string>
Expression<Func<string>>
Action
Expression<Action>
CancellationToken
as second parameter:Action
Func<TResult>
Func<Task>
Func<Task<TResult>>
Func<string>
, where TResult isstring
Action
Task.Run<string>(Func<string>, CancellationToken)
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.Why is
f(token)
not working then?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
:f(token)
isstring
, becausef
has been declared asFunc<CancellationToken,string>
.Task.Run
still take:Action
Func<TResult>
Func<Task>
Func<Task<TResult>>
How could we make it work?
This could be called like
TaskExtensions.Run(f, token)
. But I would not recommend doing that, as it provides no additional value whatsoever.Additional information:
EBNF Syntax: C# 1.0/2.0/3.0/4.0
C# Language Specification
Your delegate that you are passing into
Task.Run
does not match and any of the expected signatures. It takes in aCancellationToken
and returns astring
which does not match any of the allowed signatures.. Getting rid of the cancellation token allows it to match on of these: