异步调用异步委托?

时间:2017-02-06 12:08:40

标签: c# asynchronous async-await task-parallel-library

这是我想要做的一个愚蠢的版本:

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

那么这样做的正确方法是什么?

2 个答案:

答案 0 :(得分:3)

您应该创建两个GetResultsAsync的重载。一个人应该接受一个同步的&#39;委托,返回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);
}

第二次重载将接受异步&#39;委托,返回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));

答案 1 :(得分:0)

我说你关心的是一种风格:你想要的东西看起来更好。对于您的第一个案例,请考虑:

<html>
    <head>
        <script type="text/javascript">
          function myFunc(){
                alert(document.getElementById( 'frame' ).contentWindow.document.body.innerText);
            }

        </script>
    </head>

    <body>
        <div id="list">
            <p>
                <iframe src="x.txt" id="frame" width="50" height="50" onload="myFunc()"></iframe>
            </p>

        </div>
    </body>
</html>

理由是Plinq已经做了你想要做的事情:它并行var resultSync= numbers.AsParallel()/*.AsOrdered()*/.Select(Inc); 。对于您的第二种情况,在IEnumerables周围创建Tasks毫无意义。相当于:

Tasks

但我更喜欢谢尔盖的var resultAsync = numbers.AsParallel()./*AsOrdered().*/Select(n => IncAsync(n).Result);

也许我真正喜欢的是一对Linq风格的重载:

await Task.WhenAll(numbers.Select(IncAsync))

顺便说一句,如果您将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)

,异步版本保持在一秒左右的同步时间也会急剧上升