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

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

但这对我来说很奇怪,首先使用async和await select。根据Stephen Cleary的回答,我应该可以放弃这些。

然后是第二个Select,它选择结果。这是否意味着任务根本不是异步的,而是同步执行的(如此多的努力是徒劳的),或者任务将异步执行,当它完成时,执行查询的其余部分?

根据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个代码样本做完全相同的事情?

processevensasync看起来像这样:

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

当前回答

“你能做并不意味着你应该做。”

你可以在LINQ表达式中使用async/await,这样它就会像你想要的那样运行,但是其他阅读你的代码的开发人员还能理解它的行为和意图吗?

(特别是:异步操作应该并行运行还是故意顺序运行?最初的开发者是否考虑过这一点?)

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

其他回答

我更喜欢这个扩展方法:

public static async Task<IEnumerable<T>> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
    return await Task.WhenAll(tasks);
}

因此,它可以用于方法链接:

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

我有同样的问题@KTCheek,因为我需要它按顺序执行。然而,我想我会尝试使用IAsyncEnumerable(在。net Core 3中引入)和await 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();

在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版本能够提供更优雅的工具来处理任务的集合和集合的任务。

“你能做并不意味着你应该做。”

你可以在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;
    }
}

用法:

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

我知道任务。当我们可以并行运行时,WhenAll就是我们要走的路。