在linq select中异步等待

时间:2016-01-26 10:26:09

标签: c# linq asynchronous

我需要修改现有程序,它包含以下代码:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

但这对我来说似乎很奇怪,首先在select中使用asyncawait。根据Stephen Cleary的this answer,我应该能够放弃这些。

然后选择结果的第二个Select。这是不是意味着任务根本不是异步的并且是同步执行的(没有任何努力),或者异步执行任务,何时完成查询的其余部分?

我是否应根据another answer by Stephen Cleary编写以下代码:

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

这是完全一样的吗?

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

当我正在处理这个项目时,我想更改第一个代码示例,但我不太热衷于更改(显然工作)异步代码。也许我只是担心什么都没有,所有3个代码示例完全相同?

ProcessEventsAsync如下所示:

async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}

7 个答案:

答案 0 :(得分:127)

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();
  

但这对我来说似乎很奇怪,首先在select中使用async和await。根据Stephen Cleary的回答,我应该能够放弃这些。

Select的调用有效。这两行基本相同:

events.Select(async ev => await ProcessEventAsync(ev))
events.Select(ev => ProcessEventAsync(ev))

(关于如何从ProcessEventAsync抛出同步异常会有一些细微差别,但在此代码的上下文中它根本不重要。)

  

然后是第二个Select选择结果。这并不意味着任务根本不是异步并且是同步执行的(没有任何努力),或者任务是异步执行的,当它完成时,其余的查询是执行?

这意味着查询正在阻止。所以它并不是真正的异步。

打破它:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))

将首先为每个事件启动异步操作。然后这一行:

                   .Select(t => t.Result)

将等待这些操作一次完成一个(首先它等待第一个事件的操作,然后是下一个,然后是下一个,等等。)

这是我不关心的部分,因为它会阻止并且还会在AggregateException中包含任何例外。

  

这是完全一样的吗?

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

是的,这两个例子是等价的。它们都启动所有异步操作(events.Select(...)),然后异步等待所有操作以任何顺序(await Task.WhenAll(...))完成,然后继续完成剩余的工作(Where...)。

这两个示例都与原始代码不同。原始代码是阻塞的,并将在AggregateException中包含异常。

答案 1 :(得分:18)

现有代码正在运行,但正在阻止该线程。

.Select(async ev => await ProcessEventAsync(ev))

为每个事件创建一个新任务,但

.Select(t => t.Result)

阻止线程等待每个新任务结束。

另一方面,您的代码产生相同的结果但保持异步。

对您的第一个代码只有一条评论。这一行

var tasks = await Task.WhenAll(events...

将生成一个Task,因此该变量应以单数形式命名。

最后你的最后一个代码是相同的,但更简洁

供参考:Task.Wait / Task.WhenAll

答案 2 :(得分:6)

使用Linq中的当前方法,它看起来很丑陋:

var tasks = items.Select(
    async item => new
    {
        Item = item,
        IsValid = await IsValid(item)
    });
var tuples = await Task.WhenAll(tasks);
var validItems = tuples
    .Where(p => p.IsValid)
    .Select(p => p.Item)
    .ToList();

希望以下版本的.NET能够提供更优雅的工具来处理集合的任务和任务集合。

答案 3 :(得分:5)

我和@KTCheek 有同样的问题,我需要它按顺序执行。但是我想我会尝试使用 IAsyncEnumerable(在 .NET Core 3 中引入)并等待 foreach(在 C# 8 中引入)。这是我想出的:

public static class IEnumerableExtensions {
    public static async IAsyncEnumerable<TResult> SelectAsync<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> selector) {
        foreach (var item in source) {
            yield return await selector(item);
        }
    }
}

public static class IAsyncEnumerableExtensions {
    public static async Task<List<TSource>> ToListAsync<TSource>(this IAsyncEnumerable<TSource> source) {
        var list = new List<TSource>();

        await foreach (var item in source) {
            list.Add(item);
        }

        return list;
    }
}

这可以通过说:

var inputs = await events.SelectAsync(ev => ProcessEventAsync(ev)).ToListAsync();

更新:或者您可以添加对“System.Linq.Async”的引用,然后您可以说:

var inputs = await events
    .ToAsyncEnumerable()
    .SelectAwait(async ev => await ProcessEventAsync(ev))
    .ToListAsync();

答案 4 :(得分:1)

我想调用Select(...),但要确保它按顺序运行,因为并行运行会导致其他一些并发问题,因此我就此结束了。 我无法调用.Result,因为它将阻塞UI线程。

public static class TaskExtensions
{
    public static async Task<IEnumerable<TResult>> SelectInSequenceAsync<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> asyncSelector)
    {
        var result = new List<TResult>();
        foreach (var s in source)
        {
            result.Add(await asyncSelector(s));
        }
        
        return result;
    }
}

用法:

var inputs = events.SelectInSequenceAsync(ev => ProcessEventAsync(ev))
                   .Where(i => i != null)
                   .ToList();

我知道Task.WhenAll是我们可以并行运行的方式。

答案 5 :(得分:0)

我使用了以下代码:

1

像这样:

web_root/method/1/index.php

答案 6 :(得分:-1)

“仅仅是因为您不能代表您应该这样做。”

您可能可以在LINQ表达式中使用async / await,使其行为完全符合您的期望,但是其他任何读取您的代码的开发人员仍然可以理解其行为和意图吗?

(特别是:异步操作应该并行运行还是故意执行?原始开发人员甚至考虑过吗?)

问题也清楚地表明了这一点,似乎是开发人员在不了解其意图的情况下试图理解别人的代码而提出的。为了确保不再发生这种情况,如果可能的话,最好将LINQ表达式重写为循环语句。