Task.Run和预期的代表 [英] Task.Run and expected delegate

查看:312
本文介绍了Task.Run和预期的代表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  var f = new Func< CancellationToken, string>(uc.ViewModel.SlowProcess); 

1)(VALID)string dataPromise = await Task.Run< string>(()=> f(令牌),令牌);

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作为参数并返回字符串的方法。 / p>

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



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



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



为什么我无法通过f(token)作为委托?

解决方案

传递 f(令牌)作为一个委托其实是你在做的(1)。



()=> f(token)是一个没有参数和返回类型 string 的委托。



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



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






编辑:我们来澄清一下。


IL代码可能显示全部。


可能我们应该尝试了解代码实际意义我们可以使用 Roslyn ,.NET编译器平台:


  1. 在Visual Studio中创建一个新的单元测试项目。

  2. 显示软件包管理器控制台(从视图>其他Windows),然后输入安装包Microsoft.CodeAnalysis -Pre

  3. 创建一个包含以下代码的新类:

     使用系统; 
    使用Microsoft.CodeAnalysis;
    使用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访问(SyntaxNode node)
    {
    缩进++;
    var indents = new String('',Indent * 2);
    Console.WriteLine(indents + node.CSharpKind());
    base.Visit(node);
    缩进
    }
    }


  4. 现在,让我们创建一个测试类从上面分析你的陈述:

      [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) ,令牌));
    }


  5. 对于每种情况,我们得到一些常见的输出:

     (...)
    InvocationExpression | Task.Run< string>(...,token)
    SimpleMemberAccessExpression | Task.Run< string>
    IdentifierName | Task
    GenericName |运行< string>
    TypeArgumentList | < string>
    PredefinedType | string
    ArgumentList | (...,令牌)
    参数| ...
    (...)| ...
    参数|令牌
    IdentifierName |令牌


  6. 对于(1)和(2),我们得到以下参数: p>

      ParenthesizedLambdaExpression | ()=> ...()
    ParameterList | ()
    InvocationExpression | => ...()
    (...)| ...


  7. 对于(3),我们得到以下参数:

      InvocationExpression | f(令牌)
    IdentifierName | f
    ArgumentList | (令牌)
    参数|令牌
    IdentifierName |令牌




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



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



这是什么意思?




    < (1)和(2)中的lambdas有一个空参数列表,因此没有输入。
  • 在两个lambdas中,我们调用返回一个字符串的东西(方法或委托)。

  • 这意味着我们的lambda表达式的类型将是以下之一:

    • Func< string> ;

    • 表达式< Func< string>>

    • 动作

    • 表达式< Action>


  • 我们的Task.Run方法的第一个参数的类型确定使用哪种类型。我们有以下几种可能性,因为我们使用一个重载,将 CancellationToken 作为第二个参数:

    • 动作

    • Func< TResult>

    • < Func< Task>
    • Func< Task< TResult>> / li>

  • 有两种匹配的类型:

    • Func< string> ,其中TResult是 string

    • Action


  • 第一个优先级较高,所以我们使用过载 Task.Run< string> Func< string>,CancellationToken)



好的。这就是为什么(1)和(2)都工作:他们使用一个lambda,其实际上是生成一个委托,而代表的类型与 Task.Run 方法



为什么 f(token)不工作?


一旦你接受传递一个参数化的委托本质上被处理像传递函数它包装,一切都像你所期望的一样。


没有像参数化委托这样的东西。有代理具有参数( Action< T> Func< T,TResult> ...)与 f(token)的根本不同之处在于它是委托f的调用,这导致委托方法的返回值。这就是为什么 f(token)的类型仅仅是 string




  • InvocationExpression的类型是被调用方法的返回类型。

  • f(token)的类型是 string ,因为 f 已被声明为 Func< CancellationToken,string>

  • 我们对 Task.Run 的超载仍然需要:

    • Action

    • Func< TResult>

    • 任务>

    • Func< Task< TResult>>


  • 没有匹配。代码不能编译。



我们该如何使其工作?

  public static class TaskExtensions 
{
public static Task< TResult>运行< TResult>(Func< CancellationToken,TResult> function,CancellationToken token)
{
Func< TResult> wrappedFunction =()=>功能(令牌);
return Task.Run(wrappedFunction,token);
}
}

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



其他信息:



< a href =http://www.externsoft.ch/download/csharp.html =nofollow> EBNF语法:C#1.0 / 2.0 / 3.0 / 4.0

C#语言规范


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 type string.

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.

I would stick with what you did in (1).


Edit: Let's clarify things a bit.

IL code probably shows all.

Probably, but we should rather try to understand what the code actually means. We can do this using Roslyn, the .NET Compiler Platform:

  1. Create a new Unit Test Project in Visual Studio.
  2. Show the Package Manager Console (from View > Other Windows) and enter Install-Package Microsoft.CodeAnalysis -Pre
  3. 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--;
        }
    }
    

  4. 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)");
    }
    

  5. 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
    

  6. For (1) and (2), we get the following argument:

    ParenthesizedLambdaExpression      | () => ...()
      ParameterList                    | ()
      InvocationExpression             |    => ...()
        (...)                          |       ...
    

  7. For (3) instead, we get the following argument:

    InvocationExpression               | f(token)
      IdentifierName                   | f
      ArgumentList                     |  (token)
        Argument                       |   token
          IdentifierName               |   token
    

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?

  • Our lambdas in (1) and (2) have an empty parameter list and thereby no input.
  • In both lambdas, we invoke something (method or delegate) that returns a string.
  • That means, the type of our lambda expression will be one of the following:
    • Func<string>
    • Expression<Func<string>>
    • Action
    • Expression<Action>
  • The type of the first argument of our Task.Run method determines which type is used. We have the following possibilities, given that we use an overload that takes a CancellationToken as second parameter:
    • Action
    • Func<TResult>
    • Func<Task>
    • Func<Task<TResult>>
  • There are two matching types:
    • Func<string>, where TResult is string
    • Action
  • The first one has higher precedence, so we use the overload 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?

Once you accept that passing a parameterized delegate essentially gets treated like passing the function(s) it wraps, everything works like you would expect.

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 from f(token) which is an invocation of delegate f, which results in the return value of the delegated method. That's why the type of f(token) simply is string:

  • The type of an InvocationExpression is the return type of the called method. The same applies to delegates.
  • The type of f(token) is string, because f has been declared as Func<CancellationToken,string>.
  • Our overloads for Task.Run still take:
    • Action
    • Func<TResult>
    • Func<Task>
    • Func<Task<TResult>>
  • There is no match. Code's not compiling.

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);
    }
}

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

这篇关于Task.Run和预期的代表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆