[英]Asynchronously calling asynchronous delegate?
這是我想做的事情的精簡版:
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
。
那么正確的方法是什么呢?
您應該創建兩個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));
我想說的是您的關注點是風格上的:您想要讀起來更好的東西。 對於第一種情況,請考慮:
var resultSync= numbers.AsParallel()/*.AsOrdered()*/.Select(Inc);
基於Plinq已經完成了您想做的事情:它並行化IEnumerables
。 對於第二種情況,圍繞Tasks
創建Tasks
是沒有意義的。 等效為:
var resultAsync = numbers.AsParallel()./*AsOrdered().*/Select(n => IncAsync(n).Result);
但我更喜歡Sergey的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)
,同步的時間也會急劇上升,其中異步版本會保持一秒鍾左右
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.