Async await in linq select

匿名 (未验证) 提交于 2019-12-03 02:16:02

问题:

I need to modify an existing program and it contains following code:

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

But this seems very weird to me, first of all the use of async and awaitin the select. According to this answer by Stephen Cleary I should be able to drop those.

Then the second Select which selects the result. Doesn't this mean the task isn't async at all and is performed synchronously (so much effort for nothing), or will the task be performed asynchronously and when it's done the rest of the query is executed?

Should I write the above code like following according to another answer by Stephen Cleary:

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

and is it completely the same like this?

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

While i'm working on this project I'd like to change the first code sample but I'm not too keen on changing (apparantly working) async code. Maybe I'm just worrying for nothing and all 3 code samples do exactly the same thing?

ProcessEventsAsync looks like this:

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

回答1:

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

But this seems very weird to me, first of all the use of async and await in the select. According to this answer by Stephen Cleary I should be able to drop those.

The call to Select is valid. These two lines are essentially identical:

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

(There's a minor difference regarding how a synchronous exception would be thrown from ProcessEventAsync, but in the context of this code it doesn't matter at all.)

Then the second Select which selects the result. Doesn't this mean the task isn't async at all and is performed synchronously (so much effort for nothing), or will the task be performed asynchronously and when it's done the rest of the query is executed?

It means that the query is blocking. So it is not really asynchronous.

Breaking it down:

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

will first start an asynchronous operation for each event. Then this line:

                   .Select(t => t.Result) 

will wait for those operations to complete one at a time (first it waits for the first event's operation, then the next, then the next, etc).

This is the part I don't care for, because it blocks and also would wrap any exceptions in AggregateException.

and is it completely the same like this?

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

Yes, those two examples are equivalent. They both start all asynchronous operations (events.Select(...)), then asynchronously wait for all the operations to complete in any order (await Task.WhenAll(...)), then proceed with the rest of the work (Where...).

Both of these examples are different from the original code. The original code is blocking and will wrap exceptions in AggregateException.



回答2:

Existing code is working, but is blocking the thread.

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

creates a new Task for every event, but

.Select(t => t.Result) 

blocks the thread waiting for each new task to end.

In the other hand your code produce the same result but keeps asynchronous.

Just one comment on your first code. This line

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

will produce a single Task so the variable should be named in singular.

Finally your last code make the same but is more succinct

For reference: Task.Wait / Task.WhenAll



回答3:

With current methods available in Linq it looks quite ugly:

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

Hopefully following versions of .NET will come up with more elegant tooling to handle collections of tasks and tasks of collections.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!