C# linq select中的异步等待

C# linq select中的异步等待,c#,linq,asynchronous,C#,Linq,Asynchronous,我需要修改现有程序,它包含以下代码: var inputs = events.Select(async ev => await ProcessEventAsync(ev)) .Select(t => t.Result) .Where(i => i != null) .ToList(); 但这对我来说似乎很奇怪,首先在select中使用async和wait。根据斯蒂

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

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();
但这对我来说似乎很奇怪,首先在select中使用
async
wait
。根据斯蒂芬·克利里的说法,我应该可以放弃这些

然后第二个
选择
,选择结果。这难道不意味着任务根本不是异步的,而是同步执行的(付出了那么多的努力,却一无所获),或者任务是异步执行的,当它完成时,查询的其余部分将被执行

我是否应该按照以下方式编写上述代码:

和这完全一样吗

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();
当我在这个项目上工作时,我想更改第一个代码示例,但我不太喜欢更改(显然正在工作)异步代码。也许我只是无忧无虑,而所有3个代码示例都做了完全相同的事情

ProcessEventsSync如下所示:

async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}
var result = await sourceEnumerable.SelectAsync(async s=>await someFunction(s,other params));
异步任务ProcessEventAsync(InputEventEV){…}
现有代码正在工作,但正在阻塞线程

.Select(async ev => await ProcessEventAsync(ev))
为每个事件创建新任务,但

.Select(t => t.Result)
阻止等待每个新任务结束的线程

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

只需对您的第一个代码添加一条注释。这条线

var tasks = await Task.WhenAll(events...
将生成单个任务,因此变量应以单数命名

最后,您的上一个代码与此相同,但更简洁

供参考:/

但这对我来说似乎很奇怪,首先是在select中使用async和wait。根据斯蒂芬·克利里的回答,我应该可以放弃这些

调用
Select
是有效的。这两条线基本相同:

events.Select(async ev => await ProcessEventAsync(ev))
events.Select(ev => ProcessEventAsync(ev))
(关于如何从
ProcessEventAsync
引发同步异常,有一个细微的区别,但在这段代码的上下文中,这根本不重要。)

然后选择第二个选项,选择结果。这难道不意味着任务根本不是异步的,而是同步执行的(付出了那么多的努力,却一无所获),或者任务是异步执行的,当它完成时,查询的其余部分将被执行

这意味着查询被阻塞。所以它不是真正的异步

细分:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
将首先为每个事件启动异步操作。那么这一行:

                   .Select(t => t.Result)
将等待这些操作一次完成一个(首先等待第一个事件的操作,然后等待下一个,然后等待下一个,以此类推)

这是我不喜欢的部分,因为它会阻塞并将任何异常包装在
aggregateeexception

和这完全一样吗

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();
是的,这两个例子是等价的。它们都启动所有异步操作(
事件。选择(…)
),然后异步等待所有操作以任何顺序完成(
等待任务。当所有(…)
),然后继续其余工作(
其中…


这两个示例都不同于原始代码。原始代码正在阻塞,并将异常包装在
aggregateeexception

中,使用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将提供更优雅的工具来处理任务集合和集合任务。

我更喜欢将此作为扩展方法:

public static async Task<IEnumerable<T>> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
    return await Task.WhenAll(tasks);
}
我使用了以下代码:

public static async Task<IEnumerable<TResult>> SelectAsync<TSource,TResult>(
    this IEnumerable<TSource> source, Func<TSource, Task<TResult>> method)
{
  return await Task.WhenAll(source.Select(async s => await method(s)));
}

编辑:

有些人提出了并发性问题,比如当您访问数据库时,不能同时运行两个任务。因此,这里有一个更复杂的版本,它还允许特定的并发级别:

public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>(
    this IEnumerable<TSource> source, Func<TSource, Task<TResult>> method,
    int concurrency = int.MaxValue)
{
    var semaphore = new SemaphoreSlim(concurrency);
    try
    {
        return await Task.WhenAll(source.Select(async s =>
        {
            try
            {
                await semaphore.WaitAsync();
                return await method(s);
            }
            finally
            {
                semaphore.Release();
            }
        }));
    } finally
    {
        semaphore.Dispose();
    }
}
注意:按顺序执行任务并不意味着执行会在出现错误时停止

就像并发性值较大或未指定参数一样,所有任务都将执行,如果其中任何任务失败,则生成的AggregateException将包含抛出的异常

如果您想一个接一个地执行任务,但第一次执行失败,请尝试另一种解决方案,如xhafan()所建议的解决方案。仅仅因为您可以,并不意味着您应该这样做

您可能可以在LINQ表达式中使用async/await,这样它的行为将完全符合您的要求,但是阅读您的代码的任何其他开发人员是否仍然理解它的行为和意图

(特别是:异步操作应该并行运行还是有意按顺序运行?最初的开发人员有没有想过?)


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

我想调用
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;
    }
}

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

我和@ktcheck有相同的问题,我需要它按顺序执行。然而,我想我会尝试使用IAsyncEnumerable(在.NETCore3中引入)并等待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;
    }
}
更新:或者,您可以添加对“System.Linq.Async”的引用,然后您可以说:

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

ProceesEventAsync的返回类型是什么?@ted24它是
Task
,而
InputResult
是一个自定义类。我认为您的版本更容易阅读。但是,您忘记了在您的
Where
之前选择任务的结果,并且InputResult具有结果属性权限?@tede24 Result是任务的属性,而不是我的类的属性。@Max the await应该确保我在不访问
Result
property o的情况下获得结果
var result = await sourceEnumerable.SelectAsync(async s=>await someFunction(s,other params),1);
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();
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();
var inputs = await events
    .ToAsyncEnumerable()
    .SelectAwait(async ev => await ProcessEventAsync(ev))
    .ToListAsync();