簡體   English   中英

c#.net為什么Task.Run似乎處理Func <T> 與其他代碼不同?

[英]c# .net why does Task.Run seem to handle Func<T> differently than other code?

作為.NET 4.5一部分的新Task.Run靜態方法似乎沒有像人們期望的那樣運行。

例如:

Task<Int32> t = Task.Run(()=>5);     

編譯好,但是

Task<Int32> t = Task.Run(MyIntReturningMethod);
...
public Int32 MyIntReturningMethod() {
  return (5);
  }

抱怨MyIntReturningMethod返回了錯誤的類型。

也許我只是不了解正在調用Task.Run的哪個重載。 但在我看來,上面的lambda代碼看起來很像Func<Int32> ,而MyIntReturningMethod肯定與Func<Int32>兼容

對於發生了什么的任何想法? 邁克爾

(當然,要解決問題,只需說出Task.Run((Func<int>)MyIntReturningMethod) 。)

這與Task等完全無關。

這里需要注意的一個問題是,當存在非常多的重載時,編譯器錯誤文本將僅關注一對“重載”。 所以這很令人困惑。 原因是確定最佳過載的算法考慮了所有重載,並且當該算法斷定沒有找到最佳過載時,不會產生錯誤文本的某對重載,因為所有重載都可能(或可能不)參與過。

要了解會發生什么,請參閱此簡化版本:

static class Program
{
    static void Main()
    {
        Run(() => 5);  // compiles, goes to generic overload
        Run(M);        // won't compile!
    }

    static void Run(Action a)
    {
    }
    static void Run<T>(Func<T> f)
    {
    }
    static int M()
    {
        return 5;
    }
}

正如我們所看到的,這絕對沒有對Task引用,但仍會產生同樣的問題。

請注意,匿名函數轉換和方法組轉換(仍)不完全相同。 詳細信息可在C#語言規范中找到

lambda:

() => 5

實際上甚至不能轉換為System.Action類型。 如果您嘗試這樣做:

Action myLittleVariable = () => 5;

它將失敗並出現錯誤CS0201:只能將賦值,調用,遞增,遞減,等待和新對象表達式用作語句 因此很清楚與lambda一起使用哪個重載。

另一方面,方法組:

M

可以轉換為Func<int>Action 請記住,完全允許接收返回值,就像語句一樣:

M(); // don't use return value

本身是有效的。

這種方式回答了這個問題,但我會舉一個額外的例子來說明一個問題。 考慮這個例子:

static class Program
{
    static void Main()
    {
        Run(() => int.Parse("5"));  // compiles!
    }

    static void Run(Action a)
    {
    }
    static void Run<T>(Func<T> f)
    {
    }
}

在最后一個示例中,lambda實際上可以轉換為兩種委托類型! (只是嘗試刪除泛型重載。)對於lambda的右側,箭頭=>是一個表達式:

int.Parse("5")

這本身就是一個有效的聲明。 但是在這種情況下,重載分辨率仍然可以找到更好的過載。 正如我之前所說,檢查C#規范。


受HansPassant和BlueRaja-DannyPflughoeft的啟發,這是最后一個(我認為)的例子:

class Program
{
    static void Main()
    {
        Run(M);        // won't compile!
    }

    static void Run(Func<int> f)
    {
    }
    static void Run(Func<FileStream> f)
    {
    }

    static int M()
    {
        return 5;
    }
}

請注意,在這種情況下,絕對沒有辦法將int 5轉換為System.IO.FileStream 方法組轉換仍然失敗。 這可能與兩個普通方法int f();的事實有關int f(); FileStream f(); ,例如從兩個不同的基接口的某個接口繼承,沒有辦法解析調用f(); 返回類型不是C#中方法簽名的一部分。

我仍然避免在我的回答中介紹Task ,因為它可能會給出這個問題的錯誤印象。 人們很難理解Task ,而且它在BCL中相對較新。


這個答案已經發展了很多。 最后,事實證明這與線程中的基本問題完全相同為什么Func<T>Func<IEnumerable<T>>不明確? 我的Func<int>Func<FileStream>例子幾乎一樣清楚。 Eric Lippert在其他帖子中給出了一個很好的答案。

這應該在.Net 4.0中修復,但Task.Run()是.Net 4.5的新功能

通過添加Task.Run(Func<Task<T>>)方法,.NET 4.5有自己的重載歧義。 在C#版本5中支持async / await。它允許從T foo()Func<Task<T>>的隱式轉換。

這是async / await非常甜的語法糖,但在這里會產生空洞。 方法聲明中的async關鍵字的省略不會在方法重載選擇中被考慮,這會打開另一個苦差事框,程序員忘記在他們意圖使用異步時。 否則遵循通常的C#約定,只考慮方法簽名中的方法名稱和參數進行方法重載選擇。

需要顯式使用委托類型來解決歧義。

當您將Func<TResult>傳遞給方法Run<TResult>(Func<TResult>)您不必在methodcall上指定泛型,因為它可以推斷它。 你的lambda做了那個推斷。

但是,你的函數實際上不是Func<TResult>而lambda是。

如果你做Func<Int32> f = MyIntReturningMethod它可以工作。 現在,如果您指定Task.Run<Int32>(MyIntReturningMethod)您也希望它也能正常工作。 但是它無法確定它是否應該解析Func<Task<TResult>>重載或Func<TResult>重載,這沒有多大意義,因為很明顯該方法沒有返回任務。

如果你編譯簡單如下的東西:

void Main()
{
    Thing(MyIntReturningMethod);
}


public void Thing<T>(Func<T> o)
{
    o();
}

public Int32 MyIntReturningMethod()
{
return (5);
}

IL看起來像這樣....

IL_0001:  ldarg.0     
IL_0002:  ldarg.0     
IL_0003:  ldftn       UserQuery.MyIntReturningMethod
IL_0009:  newobj      System.Func<System.Int32>..ctor
IL_000E:  call        UserQuery.Thing

(一些額外的東西來自LINQ Pad的補充......就像UserQuery部分一樣)

IL看起來像是一個明確的演員表。 所以看起來編譯器實際上並不知道使用哪種方法。 所以它不知道自動創建什么強制轉換。

您可以使用Task.Run<Int32>((Func<Int32>)MyIntReturningMethod)來幫助它。 雖然我同意這似乎是編譯器應該能夠處理的東西。 因為Func<Task<Int32>>Func<Int32> ,所以它們會混淆編譯器是沒有意義的。

似乎是一個重載解決問題。 編譯器無法判斷您正在調用哪個重載(因為首先它必須找到要創建的正確委托,它不知道因為這取決於您正在調用的重載)。 它必須猜測和檢查,但我猜它不是那么聰明。

泰勒詹森的方法對我有用

此外,您可以使用lambda表達式嘗試此操作:

public class MyTest
{
    public void RunTest()
    {
        Task<Int32> t = Task.Run<Int32>(() => MyIntReturningMethod());
        t.Wait();
        Console.WriteLine(t.Result);
    }

    public int MyIntReturningMethod()
    {
        return (5);
    }
}

這是我的抨擊:

public class MyTest
{
    public void RunTest()
    {
        Task<Int32> t = Task.Run<Int32>(new Func<int>(MyIntReturningMethod));
        t.Wait();
        Console.WriteLine(t.Result);
    }

    public int MyIntReturningMethod()
    {
        return (5);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM