简体   繁体   English

并行运行异步方法

[英]Running async methods in parallel

I've got an async method, GetExpensiveThing() , which performs some expensive I/O work.我有一个异步方法GetExpensiveThing() ,它执行一些昂贵的 I/O 工作。 This is how I am using it:这就是我使用它的方式:

// Serial execution
public async Task<List<Thing>> GetThings()
{
    var first = await GetExpensiveThing();
    var second = await GetExpensiveThing();
    return new List<Thing>() { first, second };
}

But since it's an expensive method, I want to execute these calls in in parallel.但由于它是一种昂贵的方法,我想并行执行这些调用。 I would have thought moving the awaits would have solved this:我原以为移动等待会解决这个问题:

// Serial execution
public async Task<List<Thing>> GetThings()
{
    var first = GetExpensiveThing();
    var second = GetExpensiveThing();
    return new List<Thing>() { await first, await second };
}

That didn't work, so I wrapped them in some tasks and this works:那没有用,所以我将它们包装在一些任务中,这有效:

// Parallel execution
public async Task<List<Thing>> GetThings()
{
    var first = Task.Run(() =>
    {
        return GetExpensiveThing();
    });

    var second = Task.Run(() =>
    {
        return GetExpensiveThing();
    });

    return new List<Thing>() { first.Result, second.Result };
}

I even tried playing around with awaits and async in and around the tasks, but it got really confusing and I had no luck.我什至尝试在任务中和任务周围玩等待和异步,但它真的很混乱,我没有运气。

Is there a better to run async methods in parallel, or are tasks a good approach?并行运行异步方法是否更好,或者任务是一种好方法吗?

Is there a better to run async methods in parallel, or are tasks a good approach?并行运行异步方法是否更好,或者任务是一种好方法?

Yes, the "best" approach is to utilize the Task.WhenAll method.是的,“最佳”方法是使用Task.WhenAll方法。 However, your second approach should have ran in parallel.但是,您的第二种方法应该并行运行。 I have created a .NET Fiddle , this should help shed some light.我创建了一个.NET Fiddle ,这应该有助于说明一些问题。 Your second approach should actually be running in parallel.您的第二种方法实际上应该并行运行。 My fiddle proves this!我的小提琴证明了这一点!

Consider the following:请考虑以下事项:

public Task<Thing[]> GetThingsAsync()
{
    var first = GetExpensiveThingAsync();
    var second = GetExpensiveThingAsync();

    return Task.WhenAll(first, second);
}

Note注意

It is preferred to use the "Async" suffix, instead of GetThings and GetExpensiveThing - we should have GetThingsAsync and GetExpensiveThingAsync respectively - source .最好使用“Async”后缀,而不是GetThingsGetExpensiveThing - 我们应该分别使用GetThingsAsyncGetExpensiveThingAsync - source

Task.WhenAll() has a tendency to become unperformant with large scale/amount of tasks firing simultaneously - without moderation/throttling. Task.WhenAll()有在大规模/大量任务同时触发的情况下变得性能不佳的趋势 - 没有节制/节流。

If you are doing a lot of tasks in a list and wanting to await the final outcome, then I propose using a partition with a limit on the degree of parallelism.如果您在一个列表中执行很多任务并希望等待最终结果,那么我建议使用对并行度有限制的partition

I have modified Stephen Toub's blog elegant approach to modern LINQ:我修改了Stephen Toub 的博客对现代 LINQ 的优雅方法:

public static Task ParallelForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> funcBody, int maxDoP = 4)
{
    async Task AwaitPartition(IEnumerator<T> partition)
    {
        using (partition)
        {
            while (partition.MoveNext())
            {
                 await Task.Yield(); // prevents a sync/hot thread hangup
                 await funcBody(partition.Current);
            }
        }
    }

    return Task.WhenAll(
        Partitioner
            .Create(source)
            .GetPartitions(maxDoP)
            .AsParallel()
            .Select(p => AwaitPartition(p)));
}

How it works is simple, take an IEnumerable - dissect it into evenish partitions and the fire a function/method against each element, in each partition, at the same time.它的工作原理很简单,取一个 IEnumerable - 将它分解成均匀的分区,并同时针对每个分区中的每个元素触发一个函数/方法。 No more than one element in each partition at anyone time, but n Tasks in n partitions.任何时候每个分区中不超过一个元素,但是 n 个分区中的 n 个任务。

Extension Usage:扩展用法:

await myList.ParallelForEachAsync(myFunc, Environment.ProcessorCount);

Edit: I now keep some overloads in a repository on Github if you need more options.编辑:如果您需要更多选项,我现在在 Github 上的存储库中保留一些重载。 It's in a NuGet too for NetStandard.它也在 NetStandard 的 NuGet 中。

Edit 2: Thanks to comments from Theodor below, I was able to mitigate poorly written Async Tasks from blocking parallelism by using await Task.Yield();编辑 2:感谢下面 Theodor 的评论,我能够通过使用await Task.Yield();来缓解编写不佳的异步任务阻塞并行性await Task.Yield(); . .

You can your the Task.WhenAll , which returns when all depending tasks are done您可以使用Task.WhenAll ,它会在所有相关任务完成后返回

Check this question here for reference 在此处检查此问题以供参考

If GetExpensiveThing is properly asynchronous (meaning it doesn't do any IO or CPU work synchronously), your second solution of invoking both methods and then awaiting the results should've worked.如果GetExpensiveThing正确异步的(意味着它不同步执行任何 IO 或 CPU 工作),则调用这两种方法然后等待结果的第二个解决方案应该可以工作。 You could've also used Task.WhenAll .您也可以使用Task.WhenAll

However, if it isn't, you may get better results by posting each task to the thread-pool and using the Task.WhenAll combinator, eg:但是,如果不是,您可以通过将每个任务发布到线程池并使用Task.WhenAll组合器来获得更好的结果,例如:

public Task<IList<Thing>> GetThings() =>
    Task.WhenAll(Task.Run(() => GetExpensiveThing()), Task.Run(() => GetExpensiveThing()));

(Note I changed the return type to IList to avoid await s altogether.) (注意我将返回类型更改为IList以避免完全避免await 。)

You should avoid using the Result property.您应该避免使用Result属性。 It causes the caller thread to block and wait for the task to complete, unlike await or Task.WhenAll which use continuations.它会导致调用者线程阻塞并等待任务完成,这与使用延续的awaitTask.WhenAll不同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM