2

I have these example code:

 private async Task<IEnumerable<long>> GetValidIds1(long[] ids) { var validIds = new List<long>(); var results = await Task.WhenAll(ids.Select(i => CheckValidIdAsync(i))) .ConfigureAwait(false); for (int i = 0; i < ids.Length; i++) { if (results[i]) { validIds.Add(ids[i]); } } return validIds; } private async Task<IEnumerable<long>> GetValidIds2(long[] ids) { var validIds = new ConcurrentBag<long>(); await Task.WhenAll(ids.Select(async i => { var valid = await CheckValidIdAsync(i); if (valid) validIds.Add(i); })).ConfigureAwait(false); return validIds; } private async Task<bool> CheckValidIdAsync(long id); 

I currently use GetValidIds1() but it has inconvenience of having to tie input ids to result using index at the end.

GetValidIds2() is what i want to write but there are a few concerns:

  1. I have 'await' in select lambda expression. Because LINQ is lazy evaluation, I don't think it would block other CheckValidIdAsync() calls from starting but exactly who's context does it suspend? Per MSDN doc

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes.

So in this case, the enclosing async method is lambda expression itself so it doesn't affect other calls?

  1. Is there a better way to process result of async method and collect output of that process in a list?

1 Answer 1

2

Another way to do it is to project each long ID to a Task<ValueTuple<long, bool>>, instead of projecting it to a Task<bool>. This way you'll be able to filter the results using pure LINQ:

private async Task<long[]> GetValidIds3(long[] ids) { IEnumerable<Task<(long Id, bool IsValid)>> tasks = ids .Select(async id => { bool isValid = await CheckValidIdAsync(id).ConfigureAwait(false); return (id, isValid); }); var results = await Task.WhenAll(tasks).ConfigureAwait(false); return results .Where(e => e.IsValid) .Select(e => e.Id) .ToArray(); } 

The above GetValidIds3 is equivalent with the GetValidIds1 in your question. It returns the filtered IDs in the same order as the original ids. On the contrary the GetValidIds2 doesn't guarantee any order. If you have to use a concurrent collection, it's better to use a ConcurrentQueue<T> instead of a ConcurrentBag<T>, because the former preserves the insertion order. Even if the order is not important, preserving it makes the debugging easier.

Sign up to request clarification or add additional context in comments.

3 Comments

Alternatively just an long? and return null for your false? Like this return (await Task.WhenAll(ids.Select(i => return await CheckValidIdAsync(id).ConfigureAwait(false) ? i : null))).Where(i => i != null).ToArray() obviously you can use some intermediate variables there
@Charlieface I am not a fan of nullable types, because it restricts the T to be value type. A ValueTuple<T, bool> is more generally applicable IMHO.
Fair enough, just thought it might simplify OP's case, the option is there

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.