2017-02-06 182 views
2

這裏是什麼,我想要做一個簡單化下來的版本:異步調用異步委託?

private static int Inc(int input) 
{ 
    return input + 1; 
} 

private static async Task<int> IncAsync(int input) 
{ 
    await Task.Delay(200); 
    return input + 1; 
} 

private static async Task<IEnumerable<TResult>> GetResultsAsync<TInput, TResult>(Func<TInput, TResult> func, IEnumerable<TInput> values) 
{ 
    var tasks = values.Select(value => Task.Run(() => func(value))) 
         .ToList(); 
    await Task.WhenAll(tasks); 
    return tasks.Select(t => t.Result); 
} 

public async void TestAsyncStuff() 
{ 
    var numbers = new[] { 1, 2, 3, 4 }; 
    var resultSync = await GetResultsAsync(Inc, numbers); // returns IEnumerable<int> 
    Console.WriteLine(string.Join(",", resultSync.Select(n => $"{n}"))); 
    // The next line is the important one: 
    var resultAsync = await GetResultsAsync(IncAsync, numbers); // returns IEnumerable<Task<int>> 
} 

所以基本上,GetResultsAsync()旨在成爲一個通用的方法,將獲得一個函數的結果爲一組輸入值。在TestAsyncStuff()中,您可以看到它如何調用同步函數(Inc())。

當我想調用異步函數(IncAsync())時會出現問題。我得到的結果是IEnumerable<Task<int>>。我可以做對結果的Task.WhenAll(),那作品:

var tasksAsync = (await GetResultsAsync(IncAsync, numbers)).ToList(); 
await Task.WhenAll(tasksAsync); 
var resultAsync = tasksAsync.Select(t => t.Result); 
Console.WriteLine(string.Join(",", resultAsync.Select(n => $"{n}"))); 

但我想收緊的代碼,做await內聯。它應該是這個樣子:

var resultAsync = await GetResultsAsync(async n => await IncAsync(n), numbers); 

但也返回IEnumerable<Task<int>>!我可以這樣做:

var resultAsync = await GetResultsAsync(n => IncAsync(n).GetAwaiter().GetResult(), numbers); 

這作品...但是從我所看到的,使用的Task.GetAwaiter().GetResult()Task.Result不鼓勵。

那麼這樣做的正確方法是什麼?

+0

'var resultAsync = await GetResultsAsync(n => IncAsync(n).Result,numbers);'? –

+2

不要使用'async void',它只能用於事件處理程序。你不能等待'async void'方法 –

+3

也'等待Task.WhenAll(任務);返回任務。選擇(t => t.Result);'?爲什麼?如果所有任務都有返回類型,則'WhenAll'返回結果數組。通過清理代碼,你應該可以編寫'int [] results = await Task.WhenAll(tasks);' –

回答

3

您應該創建兩個重載GetResultsAsync。應該接受一個'同步'代表,返回TResult。此方法將包裹每個代表成任務,和異步運行它們:

private static async Task<IEnumerable<TResult>> GetResultsAsync<TInput, TResult>(
    Func<TInput, TResult> func, IEnumerable<TInput> values) 
{ 
    var tasks = values.Select(value => Task.Run(() => func(value))); 
    return await Task.WhenAll(tasks); 
} 

第二過載將接受「異步」代表,它返回Task<TResult>。這種方法不需要每個代表包裝成一個任務,因爲他們已經任務:

private static async Task<IEnumerable<TResult>> GetResultsAsync<TInput, TResult>(
    Func<TInput, Task<TResult>> func, IEnumerable<TInput> values) 
{ 
    var tasks = values.Select(value => func(value)); 
    return await Task.WhenAll(tasks); 
} 

你甚至可以調用從第一個第二個方法,以避免重複代碼:

private static async Task<IEnumerable<TResult>> GetResultsAsync<TInput, TResult>(
    Func<TInput, TResult> func, IEnumerable<TInput> values) 
{ 
    return await GetResultsAsync(x => Task.Run(() => func(x)), values); 
} 

注意:這些方法不簡化你的生活了很多。

var resultSync = await Task.WhenAll(numbers.Select(x => Task.Run(() => Inc(x)))); 
var resultAsync = await Task.WhenAll(numbers.Select(IncAsync)); 
0

我會說你的擔心是一種風格:你想要更好的東西。爲了您的第一種情況考慮:

var resultSync= numbers.AsParallel()/*.AsOrdered()*/.Select(Inc); 
對PLINQ已經做你想要做什麼理由

:它並行化IEnumerables。對於第二種情況,在Tasks附近創建Tasks毫無意義。相當於:

var resultAsync = numbers.AsParallel()./*AsOrdered().*/Select(n => IncAsync(n).Result); 

但我喜歡謝爾蓋的await Task.WhenAll(numbers.Select(IncAsync))更好。


也許我真正喜歡的是一個LINQ風格對重載:

var numbers = Enumerable.Range(1,6); 
var resultSync = await Enumerable.Range(1,6).SelectAsync(Inc); 
var resultAsync = await Enumerable.Range(1,100).SelectAsync(IncAsync); 

Console.WriteLine("sync" + string.Join(",", resultSync)); 
Console.WriteLine("async" + string.Join(",", resultAsync)); 


static class IEnumerableTasks 
{ 
    public static Task<TResult[]> SelectAsync<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> func) 
    { 
     return Task.WhenAll(source.Select(async n => await Task.Run(()=> func(n)))); 
    } 

    public static Task<TResult[]> SelectAsync<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> func) 
    { 
     return Task.WhenAll(source.Select(func)); 
    } 
} 
static int Inc(int input) 
{ 
    Task.Delay(1000).Wait(); 
    return input+1; 
} 

static async Task<int> IncAsync(int input) 
{ 
    await Task.Delay(1000); 
    return input + 1; 
} 

,順便說一下,如果你改變Range(1,6)Range(1,40)顯示異步的優勢。在我的機器上,同步的時間可以在異步版本保持在一秒左右的時間內迅速上升,即使對於Range(1, 100000)

+0

這太好了。基於上面提到的性能考慮,我最終使用'.AsParallel()'結合了你的答案和Sergey的答案(http://stackoverflow.com/a/19103047/7850)。不幸的是,我只能給你們其中一個答覆信貸...... :) –