Combine the result of two parallel tasks in one list - c#

I want to combine the result of 2 tasks in one List collection.
Make sure that- I want to run both methods in parallel.
Code:
List<Employee> totalEmployees = new List<Employee>();
Method1:
public async Task<IEnumerable<Employee>> SearchEmployeeFromDb();
Method2:
public async Task<IEnumerable<Employee>> GetEmployeeFromService();
Now, I want to hold the result of these two methods in totalEmployees field, also these 2 method should run asynchronously.

While many answers are close, the cleanest and most efficient option is using Task.WhenAll combined with SelectMany:
async Task<IEnumerable<Employee>> Combine()
{
var results = await Task.WhenAll(SearchEmployeeFromDb(), GetEmployeeFromService());
return results.SelectMany(result => result);
}
This assumes that by parallel you mean concurrently. If you wish to run these operations with multiple threads from the beginning (including the synchronous parts of the async method) you need to also use Task.Run to offload work to a ThreadPool thread:
private async Task<IEnumerable<Employee>> Combine()
{
var results =
await Task.WhenAll(Task.Run(() => SearchEmployeeFromDb()), Task.Run(() => GetEmployeeFromService()));
return results.SelectMany(result => result);
}

Start both tasks
Use Task.WhenAll to wait for both tasks to finish
Use Enumerable.Concat to combine the results
var searchEmployeesTask = SearchEmployeeFromDb();
var getEmployeesTask = GetEmployeeFromService();
await Task.WhenAll(searchEmployeesTask, getEmployeesTask);
var totalEmployees = searchEmployeesTask.Result.Concat(getEmployeesTask.Result);

You can use Task.WhenAll to create a task which will return when all supplied tasks are complete
var result = await Task.WhenAll(SearchEmployeeFromDb(),GetEmployeeFromService());
var combined = result[0].Concat(result[1]);

Something like this should work:
var t1 = SearchEmployeeFromDb()
var t2 = GetEmployeeFromService()
await Task.WhenAll(t1, t2)
// Now use t1.Result and t2.Result to get `totalEmployees`

Use ConfigureAwait(false) to avoid deadlocking, define the tasks, execute and then await.
var fromDbTask = SearchEmployeeFromDb().ConfigureAwait(false);
var fromServiceTask = GetEmployeeFromService().ConfigureAwait(false);
var fromDbResult = await fromDbTask;
var totalEmployees = new List(fromDbResult);
var fromServiceResult = await fromServiceResult;
totalEmployees.AddRange(fromServiceResult);
... or use whichever way you want to merge the two lists.
I updated the solution, it was unneccessary to create the list and then append the first result. We wait for the first method to finish and then create the list.

Related

Collecting results of async within lambda

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:
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?
Is there a better way to process result of async method and collect output of that process in a list?
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.

How to choose the return type of an async delegate

I am passing an async delegate to the LINQ Select method, and I would prefer to get a list of ValueTasks instead of a list of Tasks. How can I do it? Example:
var result = (new[] { 0 }).Select(async x => await Task.Yield()).ToArray();
Console.WriteLine($"Result type: {result.GetType()}");
Result type: System.Threading.Tasks.Task[]
This is not desirable. I figured out that I can create the list I want by replacing the async delegate with an async method, like this:
var result = (new[] { 0 }).Select(DoAsync).ToArray();
Console.WriteLine($"Result type: {result.GetType()}");
async ValueTask DoAsync(int arg)
{
await Task.Yield();
}
Result type: System.Threading.Tasks.ValueTask[]
This works but it's awkward. Is there any way to keep the neat delegate syntax, and still get the ValueTasks I want?
You can explicitly write value task like this
var result = (new[] { 0 }).Select<int, ValueTask>(async x => await Task.Yield()).ToArray();

Captured Variable in Lambda Task Not Updating

I have a list of headlines that I am trying to process by using a Task that updates a parameter of the headline object. The code I am trying to do it with does not actually populate the parameter properly. When I debug it, I can see that the setters are being activated and properly updating the backing fields, but when examined after Task.WhenAll , none of the properties are in fact set to their expected values.
//**Relevant signatures are:**
class Headline{
public Uri Uri { get; set; }
public IEnumerable<string> AttachedEmails { get; set; } = Enumerable.Empty<string>();
}
async Task<IEnumerable<string>> GetEmailsFromHeadline(Uri headlineUri) {
//bunch of async fetching logic that populates emailNodes correctly
return emailNodes.Select(e => e.InnerText).ToList();
}
//**Problem Area**
//Initiate tasks that start fetching information
var taskList =
postData
.Select(e => new HttpRequest() { Uri = actionUri, PostData = e })
.Select(e => Task.Run(() => GetHeadlines(e)))
.ToList();
//Wait till complete
await Task.WhenAll(taskList);
//Flatten list
var allHeadlines =
taskList
.SelectMany(e => e.Result.ToList());
//After this section of code I expect every member AttachedEmails property to be properly updated but this is not what is happening.
var headlineProcessTaskList =
allHeadlines
.Select(e => Task.Run( new Func<Task>( async () => e.AttachedEmails = await GetEmailsFromHeadline(e.Uri) ) ) )
.ToList();
await Task.WhenAll(headlineProcessTaskList);
There isn't enough code to reproduce the problem. However, we can guess that:
allHeadlines is IEnumerable<Headline>
Since this is IEnumerable<T>, it is possible that it is a LINQ query that has been deferred. Thus, when you enumerate it once in the ToList call, it creates Headline instances that have their AttachedEmails set. But when you enumerate it again later, it creates new Headline instances.
If this is correct, then one solution would be to change the type of allHeadlines to List<Headline>.
A similar problem could occur in GetEmailsFromHeadline, which presumably returns Task<IEnumerable<string>> and not IEnumerable<string> as stated. If the enumerable is deferred, then the only asynchronous part is defining the query; executing it would be after the fact - more specifically, outside the Task.Run. You might want to consider using ToList there, too.
On a side note, the new Func is just noise, and the wrapping of an asynchronous task in Task.Run is quite unusual. This would be more idiomatic:
var headlineProcessTaskList =
allHeadlines
.Select(async e => e.AttachedEmails = await GetEmailsFromHeadline(e.Uri) )
.ToList();
or, if the Task.Run is truly necessary:
var headlineProcessTaskList =
allHeadlines
.Select(e => Task.Run( () => e.AttachedEmails = (await GetEmailsFromHeadline(e.Uri)).ToList() ) )
.ToList();

Run method in parallel in C# and collate results

I have a method that returns a object. In my parent function I have a list of IDs.
I would like to call the method for each ID I have and then have the objects added to a list. Right now I have written a loop that calls the method passing each ID and waits for the returned object and then goes to the next ID.
Can this be done in parallel? Any help here would be most helpful.
Something like this maybe:
List<int> ids = new List<int>();
List<object> result = new List<object>();
Parallel.ForEach(ids, (id, state, index) => {
result.Add(new { Id = id }); // You class instance here.
});
I think Task parallel libraries will help you
Task[] tasks = new Task[2];
tasks[0] = Task.Factory.StartNew(() => YourFunction());
tasks[1] = Task.Factory.StartNew(() => YourFunction());
Task.WaitAll(tasks);// here it will wait untill all the functions get completed

How to combine the results of multiple async calls

public async Task<List<string>> getAllQueries()
{
List<string> allQueries = new List<string>();
for (int i =0 ; i < 10; i++)
{
List<string> queries = await getQueriesForId(i);
allQueries.AddRange(queries);
}
return allQueries;
}
Is there anything wrong with this code. I am not getting the correct results. I have not much idea about async/await. I observed that this function is returning the list without combining the results from all concurrent calls. Could somebody please let me know how to combine the lists coming from all concurrent calls and then only return ?
I would use the Task.WhenAll method and combine the results once they have all be materialized, consider the following:
public async Task<List<string>> GetAllQueriesAsync()
{
var tasks =
Enumerable.Range(0, 10)
.Select(i => GetQueriesForIdAsync(i));
await Task.WhenAll(tasks);
return tasks.SelectMany(t => t.Result).ToList();
}
With the following snippet there are several key changes that I made.
Suffix Task and Task<T> returning methods with "Async"
Utilized Enumerable.Range instead of for loop
This will return a list of IEnumerable<Task<List<string>>>
Run all the queries in parallel
I would recommend using Task.WhenAll(). I created these handy extension methods you might find useful:
public static Task<TResult[]> SelectAsync<T, TResult>(
this IEnumerable<T> list,
Func<T, Task<TResult>> functionToPerform)
{
var tasks = list.Select(functionToPerform.Invoke);
return Task.WhenAll(tasks);
}
And here's an example how to use it:
var results = await myItems.SelectAsync(item => DoStuff(item)).ConfigureAwait(false);

Categories

Resources