【问题标题】:Asynchronously calling asynchronous delegate?异步调用异步委托?
【发布时间】:2017-06-23 08:59:22
【问题描述】:

这是我想做的简化版:

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&lt;Task&lt;int&gt;&gt; 类型。我可以对那个结果做一个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&lt;Task&lt;int&gt;&gt;!我可以这样做:

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

这行得通...但据我所知,不鼓励使用Task.GetAwaiter().GetResult()Task.Result

那么正确的做法是什么?

【问题讨论】:

  • var resultAsync = await GetResultsAsync(n =&gt; IncAsync(n).Result, numbers);?
  • 不要使用async void,它只用于事件处理程序。你不能等待 async void 方法
  • 还有await Task.WhenAll(tasks); return tasks.Select(t =&gt; t.Result);?为什么?如果所有任务都有返回类型,WhenAll 返回结果数组。通过清理代码,您应该能够编写int[] results=await Task.WhenAll(tasks);
  • 你到底想做什么?如果您最终致电Task.Run,为什么不使用Parallel.ForTask.Run 不会做任何异步操作,它会在后台运行作业
  • Parallel.For() 是用于Actions,而不是Funcs?我会说 Plinq 是为Funcs

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


【解决方案1】:

您应该创建两个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&lt;TResult&gt;。此方法不需要将每个委托包装到一个任务中,因为它们已经是任务:

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));

【讨论】:

    【解决方案2】:

    我想说的是,您关心的是文体问题:您想要读起来更好的东西。对于您的第一种情况,请考虑:

    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)

    【讨论】:

    • 这很棒。基于提到的性能考虑here,我最终将您的答案与谢尔盖的答案结合使用.AsParallel()。不幸的是,我只能给你们中的一个回答信用... :)
    猜你喜欢
    • 1970-01-01
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 2014-11-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多