簡體   English   中英

異步調用異步委托?

[英]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.

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