[英]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>
,所以它們會混淆編譯器是沒有意義的。
似乎是一個重載解決問題。 編譯器無法判斷您正在調用哪個重載(因為首先它必須找到要創建的正確委托,它不知道因為這取決於您正在調用的重載)。 它必須猜測和檢查,但我猜它不是那么聰明。
這是我的抨擊:
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.