2014-12-05 29 views
1

我不確定如何理解以下觀察結果。Task.Run和預期的代理

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'System.Threading.Tasks.Task.Run(System.Func>,System.Threading.CancellationToken)'的最佳重載方法匹配有一些無效參數

錯誤2參數1:不能從「字串」到「System.Func>」

爲何無法通過F(令牌)作爲代表轉換?如果我用一個不帶參數的方法來做,它也可以。

回答

2

傳遞f(token)作爲代表實際上是你在做什麼(1)。

() => f(token)是一個沒有參數且返回類型爲string的委託。

f(token)不是委託,而是立即調用返回字符串的方法f。這意味着,您的代碼不會被任務基礎結構調用,而是在您創建任務之前,您自己就會產生一個字符串。您不能從該字符串創建任務,這會導致語法錯誤。

我會堅持你在(1)中所做的。


編輯:讓我們來澄清一點。

IL代碼可能顯示所有。

也許,但我們應該試着去理解代碼的實際含義。我們可以使用.NET編譯器平臺Roslyn執行此操作:

  1. 在Visual Studio中創建一個新的單元測試項目。
  2. 顯示包管理器控制檯(從視圖>其他窗口),然後輸入Install-Package Microsoft.CodeAnalysis -Pre
  3. 創建包含以下代碼的新類:

    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. 現在,讓我們創建一個測試類和分析報表從上面:

    [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. 對於每一種情況下,我們得到了一些常見的輸出:

    (...) 
        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. 對於(1)和(2),我們得到以下參數:

    ParenthesizedLambdaExpression  |() => ...() 
        ParameterList     |() 
        InvocationExpression    | => ...() 
        (...)       |  ... 
    
  7. 對於(3)代替,我們得到以下參數:

    InvocationExpression    | f(token) 
        IdentifierName     | f 
        ArgumentList      | (token) 
        Argument      | token 
         IdentifierName    | token 
    
好的,我們在這裏有什麼?

A ParenthesizedLambdaExpression顯然是內聯方法聲明。該表達式的類型由參數列表(輸入),拉姆達主體(輸出)的類型和預期類型確定,其中使用拉姆達(類型推斷)。

這是什麼意思?

  • 我們在(1)和(2)中的lambdas有一個空的參數列表,因此沒有輸入。
  • 在這兩個lambda中,我們調用返回字符串的東西(方法或委託)。
  • 這意味着,我們lambda表達式的類型將是下列之一:
    • Func<string>
    • Expression<Func<string>>
    • Action
    • Expression<Action>
  • 類型的第一個參數的我們的Task.Run方法確定使用哪種類型。我們有以下的可能性,因爲我們使用的重載需要CancellationToken作爲第二個參數:
    • Action
    • Func<TResult>
    • Func<Task>
    • Func<Task<TResult>>
  • 有兩種匹配類型:
    • Func<string>,其中TResult是string
    • Action
  • 第一個具有更高的優先級,所以我們使用過載Task.Run<string>(Func<string>, CancellationToken)

好。這就是爲什麼(1)和(2)都起作用:他們使用lambda,實際上會生成一個委託,委託的類型與方法的期望相匹配。

爲什麼f(token)不能正常工作呢?

一旦你接受的是傳遞一個參數代表基本上是被當作傳遞函數(S)後包裝,一切就像你所期望的。

有沒有這樣的事情作爲「參數化委託」。有代表具有參數(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>
    • 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)。但我不會推薦這麼做,因爲它沒有提供任何額外的價值。

其他信息:

EBNF Syntax: C# 1.0/2.0/3.0/4.0
C# Language Specification

+0

感謝。我知道你說的是正確的,但f是Func的一個實例,所以它是一個委託。好像你需要將委託包裝在一個委託中(即,將f包裝在lambda中)以使其正確工作。似乎應該有一種方法來傳遞f而不用將它包裝在lambda中。 – Bill 2014-12-05 04:38:33

+0

@Bill:再看看代碼'Task.Run (f(token),token)'。正如弗蘭克所說,代碼'f(token)'不會傳遞'f'作爲代表;它*調用*'f'。如果你想通過'f'作爲委託,你必須執行'Task.Run (f,token)'。 – 2014-12-05 13:32:59

+0

@Stephen:是的,同意了。所以,lambda表達式工作的原因是在(1)中,由於賦予給f的參數,lambda基本上包裝了函數uc.ViewModel.SlowProcess - 就像在(2)中一樣。當f被參數化時,它就像它所包裝的函數一樣對待,而當它只是f時,它被視爲一個del的引用。如果我創建了一個多播委託multiF,()=> multiF(token)也可以。一旦你接受傳遞一個參數化的委託本質上得到像傳遞函數一樣的對待,那麼所有的工作就像你期望的那樣。 IL代碼可能顯示所有。 – Bill 2014-12-05 15:57:57

0

你的代表,你傳遞到Task.Run不匹配和任何預期的簽名。它需要一個CancellationToken並返回一個string,它不符合allowed signatures.中的任何一個。擺脫取消標記的允許它來匹配這些:

Run<TResult>(Func<TResult>) 
Run<TResult>(Func<TResult>, CancellationToken)