.NET(C#): Task.Unwrap擴充方法和async Lambda

來源:互聯網
上載者:User

目錄

  • Task.Unwrap基本使用
  • Task.Factory.StartNew和Task.Run的Unwrap操作
  • 使用案例:LINQ中的async Lambda

 

返回目錄

Task.Unwrap基本使用

這個擴充方法定義在TaskExtensions類型中,命名空間在System.Threading.Tasks。Unwrap會把嵌套的Task<Task>或者Task<Task<T>>的結果提取出來。

 

就像這樣,不用Unwrap的話:

static void Main(string[] args)

{

    doo();

    Task.Delay(-1).Wait();

}

 

static async void doo()

{

    //運行嵌套的Task

    //Task返回Task<Task<string>>

    //第一個await後result類型為Task<string>

    var result = await Task.Run<Task<string>>(() =>

        {

            var task = Task.Run<string>(() =>

                {

                    Task.Delay(1000).Wait();

                    return "Mgen";

                });

            return task;

        });

 

    //第二個await後才會返回string

    Console.WriteLine(await result);

}

 

使用Unwrap後,結果可以直接從嵌套Task中提取出來:

 

static async void doo()

{

    //運行嵌套的Task

    //Task返回Task<Task<string>>

    //await後類型為Task<string>,Unwrap後result類型為string

    var result = await Task.Run<Task<string>>(() =>

        {

            var task = Task.Run<string>(() =>

                {

                    Task.Delay(1000).Wait();

                    return "Mgen";

                });

            return task;

        }).Unwrap();

 

    //不需要await,result已經是string

    Console.WriteLine(result);

}

 

 

返回目錄

Task.Factory.StartNew和Task.Run的Unwrap操作

簡單地講,Task.Factory.StartNew和Task.Run區別之一就有Task.Run會自動執行Unwrap操作,但是Task.Factory.StartNew不會,Task.Run就是Task.Factory.StartNew的更人性化封裝,而Task.Factory.StartNew則是原始的執行。(另外關於更多的區別,推薦PFX Team的一篇非常給力的文章:Task.Run vs Task.Factory.StartNew)。

 

通過代碼來驗證:

var task1 = Task.Factory.StartNew(async () => "Mgen");

var task2 = Task.Run(async () => "Mgen");

 

Console.WriteLine(task1.GetType());

Console.WriteLine(task2.GetType());

 

輸出:

System.Threading.Tasks.Task`1[System.Threading.Tasks.Task`1[System.String]]

System.Threading.Tasks.UnwrapPromise`1[System.String]

 

可以看到

使用Task.Factory.StartNew會返回原始的Task<Task<string>>。但是Task.Run則會直接返回async Lambda的結果,中間的Unwrap操作會自動進行。

 

 

返回目錄

使用案例:LINQ中的async Lambda

文章講述到這裡,或許讀者在想上述情況會不會很少見?不,任何使用async Lambda的情況都可能會出現上述情況,比如最近在搞一個WinRT的項目,使用LINQ去轉換一些資料,但是許多WinRT的API只有非同步執行的,這類問題就會出現,下方樣本:

 

我們來進行一個再簡單不過的LINQ Select操作,把一堆int轉換成string,只不過轉換過程是非同步,來看代碼:

static void Main(string[] args)

{

    doo();

    Task.Delay(-1).Wait();

}

 

static void doo()

{

    //int資料

    var ints = Enumerable.Range(1, 10);

    //轉換並輸出結果

    foreach (var str in ints.Select(async i => await Int2StringAsync(i)))

        Console.WriteLine(str);

}

 

//非同步將int轉換成string

static async Task<string> Int2StringAsync(int i)

{

    return await Task.Run<string>(() => i.ToString());

}

 

上面代碼正確嗎?很多人會認為沒問題的,Select方法通過一個async Lambda調用非同步轉換方法,並使用await非同步等待結果,那麼async Lambda返回string,str類型也是string,最後輸出所以字串。

 

但事實上程式運行後會輸出:

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

System.Threading.Tasks.Task`1[System.String]

 

str變數根本不是string,而是Task<string>。上邊的推斷錯在(上面黃字標註的)“async Lambda返回string”,async Lambda的結果並沒有被await,上面的await僅僅是對Int2StringAsync方法的非同步等待,而async Lambda本身仍然會返回Task<string>,所以Select會返回一些列的Task<string>。

 

最簡單的解決方案是,在處理結果的時候加上await,如下:

//str的類型事實上是:Task<string>

Console.WriteLine(str);

 

這是最好的方法(如果能這樣的話),因為有了這個await,事實上整個轉換過程就非同步化了!

 

當然如果你想在LINQ Select中直接返回結果string而不是Task<string>:

那麼還有一種解決方案就是不使用async Lambda,就不存在嵌套Task的問題,直接在Select中返回非同步方法呼叫的Task的Result屬性:

//int資料

var ints = Enumerable.Range(1, 10);

//Select調用非同步方法呼叫

IEnumerable<string> strs = ints.Select(i => Int2StringAsync(i).Result);

 

如果一定要使用async Lambda,則必須將嵌套的Task進行Unwrap。(當然這裡更多的是為了討論技術本身,實際工作中沒必要這麼鑽牛角尖呵呵。)

結合上面講到的知識,使用Task.Factory.StartNew需要進行一個Unwrap,然後返回Task<T>的結果作為Select方法的最終傳回值,代碼:

//int資料

var ints = Enumerable.Range(1, 10);

//Select調用非同步方法呼叫

IEnumerable<string> strs = ints.Select(i =>

    Task.Factory.StartNew(async () => await Int2StringAsync(i)).Unwrap().Result);

 

而Task.Run的話,不需要Unwrap:

IEnumerable<string> strs = ints.Select(i =>

    Task.Run(async () => await Int2StringAsync(i)).Result);

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.