Assign property, when Task completes - c#

I want to ask you: I have code:
var task1 = await _connectionService.ValidateUriAsync(uri1);
OutputResult("ss", task1);
var task2 = await _connectionService.ValidateUriAsync(uri2);
OutputResult("bb", task2);
var task3 = await _connectionService.ValidateUriAsync(uri3);
OutputResult("cc", task3);
Now I'm waiting until each task finishes and then I output the result. But I would like to run all tasks independently (I know how to do that). But what I don't know is, when some task is completed I need to output result for each task. If task fails the output will be - task1 failed or Task1 success.
I tried this solution, but I will have to check which task is completed and than its result (true/false). It is complex. If I had 100 tasks, I cannot have 100 conditions.
var tasks = new[] {task1, task2, task3};
var process = tasks.Select(async task =>
{
var result = await task;
if(task == task1)assign property
});
await Task.WhenAll(proces);
EDIT:
Here is ValidateUriAsync func:
public async Task<bool> ValidateUriAsync(Uri uri)
{
try
{
var request = WebRequest.CreateHttp(uri);
var result = await request.GetResponseAsync();
return true;
}
catch (Exception e)
{
return false;
}
}

when some task is completed I need to output result for each task.
Don't think about this in terms of "reacting to tasks as they complete". Instead, think of your ValidateUriAsync method as an "operation", and what you want is to create a new higher-level "operation" that is "validate and assign".
With that mindset, the solution is more clear - introduce a new async method for the new operation:
private async Task ValidateAndOutputResult(Uri uri, string name)
{
var result = await _connectionService.ValidateUriAsync(uri);
OutputResult(name, result);
}
Now you can call the higher-level method, and use Task.WhenAll:
var tasks = new[]
{
ValidateAndOutputResult(uri1, "ss"),
ValidateAndOutputResult(uri2, "bb"),
ValidateAndOutputResult(uri3, "cc"),
};
await Task.WhenAll(tasks);

Related

Get results after Task.WhenAll() call

Task.WhenAll(params System.Threading.Tasks.Task[] tasks) returns Task, but what is the proper way to asquire task results after calling this method?
After awaiting that task, results can be acquired from the original task by awaiting it once again which should be fine as tasks are completed already. Also it is possible to get result using Task.Result property which is often considered not good practice
Task<TResult1> task1= ...
Task<TResult2> task2= ...
Task<TResult3> task3= ...
await Task.WhenAll(task1, task2, task3)
var a = task1.Result; // returns TResult1
var b = await task1; // also returns TResult1
Which one should I choose here and why?
If you really have only an IEnumerable<Task<TResult>> and the task will be created on-the-fly (e.g. due to a .Select()) you would execute your tasks two times.
So, be sure that you either give an already materialized collection to Task.WhenAll() or get the result from the return value of that method:
var someTasks = Enumerable.Range(1, 10).Select(i => { Task.Delay(i * 100); return i; });
// Bad style, cause someTasks is an IEnumerable created on-the-fly
await Task.WhenAll(someTasks);
foreach(var task in someTasks)
{
var taskResult = await task;
Console.WriteLine(taskResult);
}
// Okay style, cause tasks are materialized before waiting, but easy to misuse wrong variable name.
var myTasks = someTasks.ToList();
await Task.WhenAll(myTasks);
foreach(var task in myTasks)
{
Console.WriteLine(task.Result);
}
// Best style
var results = await Task.WhenAll(someTasks);
foreach(var result in results)
{
Console.WriteLine(result);
}
Update
Just read this in your question:
However, I could not find any overload that would return anything but Task.
This happens, if the collection of tasks you give to the Task.WhenAll() method don't share a common Task<T> type. This could happen, if you e.g. want to run two tasks in parallel, but both return a different value. In that case you have to materialize the tasks and afterwards check the results individually:
public static class Program
{
public static async Task Main(string[] args)
{
var taskOne = ReturnTwo();
var taskTwo = ReturnPi();
await Task.WhenAll(taskOne, taskTwo);
Console.WriteLine(taskOne.Result);
Console.WriteLine(taskTwo.Result);
Console.ReadKey();
}
private static async Task<int> ReturnTwo()
{
await Task.Delay(500);
return 2;
}
private static async Task<double> ReturnPi()
{
await Task.Delay(500);
return Math.PI;
}
}
Here is the overload that returns Task<TResult[]> - MS Docs
Example:
static async Task Test()
{
List<Task<string>> tasks = new List<Task<string>>();
for (int i = 0; i < 5; i++)
{
var currentTask = GetStringAsync();
tasks.Add(currentTask);
}
string[] result = await Task.WhenAll(tasks);
}
static async Task<string> GetStringAsync()
{
await Task.Delay(1000);
return "Result string";
}

Is there a way to wait for all tasks until a specific result is true, and then cancel the rest?

In my C# console application, I'm trying to run multiple tasks that do various data checks simultaneously. If one of the tasks returns true I should stop the other tasks since I have my actionable result. It's also very possible none of the functions return true
I have the code to run the tasks together (I think), I'm just having trouble getting to the finish line:
Task task1 = Task.Run(() => Task1(stoppingToken));
Task task2 = Task.Run(() => Task2(stoppingToken));
Task task3 = Task.Run(() => Task3(stoppingToken));
Task task4 = Task.Run(() => Task4(stoppingToken));
Task task5 = Task.Run(() => Task5(stoppingToken));
Task task6 = Task.Run(() => Task6(stoppingToken));
Task.WaitAll(task1, task2, task3, task4, task5, task6);
This is a little different than the answer in the linked question where the desired result is known (timeout value). I'm waiting for any of these tasks to possibly return true and then cancel the remaining tasks if they are still running
Task.WhenAny with cancellation of the non completed tasks and timeout
Here's a solution based on continuation tasks. The idea is to append continuation tasks to each of the original (provided) tasks, and check the result there. If it's a match, the completion source will be set with a result (if there's no match, the result won't be set at all).
Then, the code will wait for whatever happens first: either all the continuation tasks complete, or the task completion result will be set. Either way, we'll be ready to check the result of the task associated with task completion source (that's why we wait for the continuation tasks to complete, not the original tasks) and if it's set, it's pretty much an indication that we have a match (the additional check at the end is a little paranoid, but better safe than sorry I guess... :D)
public static async Task<bool> WhenAnyHasResult<T>(Predicate<T> isExpectedResult, params Task<T>[] tasks)
{
const TaskContinuationOptions continuationTaskFlags = TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent;
// Prepare TaskCompletionSource to be set only when one of the provided tasks
// completes with expected result
var tcs = new TaskCompletionSource<T>();
// For every provided task, attach a continuation task that fires
// once the original task was completed
var taskContinuations = tasks.Select(task =>
{
return task.ContinueWith(x =>
{
var taskResult = x.Result;
if (isExpectedResult(taskResult))
{
tcs.SetResult(taskResult);
}
},
continuationTaskFlags);
});
// We either wait for all the continuation tasks to be completed
// (it's most likely an indication that none of the provided tasks completed with the expected result)
// or for the TCS task to complete (which means a failure)
await Task.WhenAny(Task.WhenAll(taskContinuations), tcs.Task);
// If the task from TCS has run to completion, it means the result has been set from
// the continuation task attached to one of the tasks provided in the arguments
var completionTask = tcs.Task;
if (completionTask.IsCompleted)
{
// We will check once more to make sure the result is set as expected
// and return this as our outcome
var tcsResult = completionTask.Result;
return isExpectedResult(tcsResult);
}
// TCS result was never set, which means we did not find a task matching the expected result.
tcs.SetCanceled();
return false;
}
Now, the usage will be as follows:
static async Task ExampleWithBooleans()
{
Console.WriteLine("Example with booleans");
var task1 = SampleTask(3000, true);
var task2 = SampleTask(5000, false);
var finalResult = await TaskUtils.WhenAnyHasResult(result => result == true, task1, task2);
// go ahead and cancel your cancellation token here
Console.WriteLine("Final result: " + finalResult);
Debug.Assert(finalResult == true);
Console.WriteLine();
}
What's nice about putting it into a generic method, is that it works with any type, not only booleans, as a result of the original task.
Assuming your tasks return bool you can do something like this:
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken stoppingToken = source.Token;
Task<bool> task1 = Task.Run(() => Task1(stoppingToken));
....
var tasks = new List<Task<bool>>
{
task1, task2, task3, ...
};
bool taskResult = false;
do
{
var finished = await Task.WhenAny(tasks);
taskResult = finished.Result;
tasks.Remove(finished);
} while (tasks.Any() && !taskResult);
source.Cancel();
You could use an asynchronous method that wraps a Task<bool> to another Task<bool>, and cancels a CancellationTokenSource if the result of the input task is true. In the example below this method is the IfTrueCancel, and it is implemented as local function. This way it captures the CancellationTokenSource, and so you don't have to pass it as argument on every call:
var cts = new CancellationTokenSource();
var stoppingToken = cts.Token;
var task1 = IfTrueCancel(Task.Run(() => Task1(stoppingToken)));
var task2 = IfTrueCancel(Task.Run(() => Task2(stoppingToken)));
var task3 = IfTrueCancel(Task.Run(() => Task3(stoppingToken)));
var task4 = IfTrueCancel(Task.Run(() => Task4(stoppingToken)));
var task5 = IfTrueCancel(Task.Run(() => Task5(stoppingToken)));
var task6 = IfTrueCancel(Task.Run(() => Task6(stoppingToken)));
Task.WaitAll(task1, task2, task3, task4, task5, task6);
async Task<bool> IfTrueCancel(Task<bool> task)
{
bool result = await task.ConfigureAwait(false);
if (result) cts.Cancel();
return result;
}
Another, quite different, solution to this problem could be to use PLINQ instead of explicitly created Tasks. PLINQ requires an IEnumerable of something in order to do parallel work on it, and in your case this something is the Task1, Task2 etc functions that you want to invoke. You could put them in an array of Func<CancellationToken, bool>, and solve the problem this way:
var functions = new Func<CancellationToken, bool>[]
{
Task1, Task2, Task3, Task4, Task5, Task6
};
bool success = functions
.AsParallel()
.WithDegreeOfParallelism(4)
.Select(function =>
{
try
{
bool result = function(stoppingToken);
if (result) cts.Cancel();
return result;
}
catch (OperationCanceledException)
{
return false;
}
})
.Any(result => result);
The advantage of this approach is that you can configure the degree of parallelism, and you don't have to rely on the ThreadPool availability for limiting the concurrency of the whole operation. The disadvantage is that all functions should have the same signature. You could overcome this disadvantage by declaring the functions as lambda expressions like this:
var functions = new Func<CancellationToken, bool>[]
{
ct => Task1(arg1, ct),
ct => Task2(arg1, arg2, ct),
ct => Task3(ct),
ct => Task4(arg1, arg2, arg3, ct),
ct => Task5(arg1, ct),
ct => Task6(ct)
};

Task.WhenAll but process results one by one

Let's say I have a list of Tasks, and I want to run them in parallel. But I don't need all of them to finish to continue, I can move on with just one. The following code waits for all the tasks to finish to move on. Is there a way for me to individually continue with the task that has completed while waiting for the other ones to finish?
List<string>[] x = await Task.WhenAll(new Task<List<string>>[] { task1, task2 })
// When task1 finishes, I want to process the result immediately
// instead of waiting on task2.
You're probably looking for Task.WhenAny.
I've used it for setting off a pile of tasks and then processing each of them as they become ready, but I suppose you could also just wait for one to finish and continue without the loop if you don't care about dealing with the rest.
while(tasks.Count() > 0)
{
var task = await Task.WhenAny(tasks);
tasks.Remove(task);
var taskresult = await task;
// process result
}
If you are using C# 8 and .NET Core you can take advantage of IAsyncEnumerable to hide this complexity from the consuming side.
Just like this:
static async Task Main(string[] args)
{
await foreach (var data in GetData())
{
Console.WriteLine(data);
}
Console.ReadLine();
}
static async IAsyncEnumerable<string> GetData()
{
List<Task<string>> tasks = new List<Task<string>> {GetData1(), GetData3(), GetData2()};
while (tasks.Any())
{
var finishedTask = await Task.WhenAny(tasks);
tasks.Remove(finishedTask);
yield return await finishedTask;
}
}
static async Task<string> GetData1()
{
await Task.Delay(5000);
return "Data1";
}
static async Task<string> GetData2()
{
await Task.Delay(3000);
return "Data2";
}
static async Task<string> GetData3()
{
await Task.Delay(2000);
return "Data3";
}
You can use Task.WhenAny instead.
Example "stolen" from Stephen Cleary's Blog:
var client = new HttpClient();
string results = await await Task.WhenAny(
client.GetStringAsync("http://example.com"),
client.GetStringAsync("http://microsoft.com"));
// results contains the HTML for whichever website responded first.
Responding to comment
You absolutely can keep track of the other tasks:
// supposing you have a list of Tasks in `myTasks`:
while( myTasks.Count > 0 )
{
var finishedTask = await Task.WhenAny(myTasks);
myTasks.Remove(finishedTask);
handleFinishedTask(finishedTask); // assuming this is a method that
// does the work on finished tasks.
}
The only thing you'd have to watch out for is :
The returned task will always end in the RanToCompletion state with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state.
Remarks in WhenAny Doks(Emphasis by me)
In case you want to process the results in order of the completion of the tasks, there is the OrderByCompletion extension method that does exactly that in Stephen Cleary's Nito.AsyncEx library, with the signature below:
// Creates a new collection of tasks that complete in order.
public static List<Task<T>> OrderByCompletion<T>(this IEnumerable<Task<T>> #this);
Usage example:
Task<string>[] tasks = new[] { task1, task2, task3, task4 };
foreach (var task in tasks.OrderByCompletion())
{
string result = await task;
// Do something with result
}
If you prefer not having external dependencies, the source code is here.
Based on the answer of Peter Csala, here a extension method for IAsyncEnumerable:
public static async IAsyncEnumerable<T> OrderedByCompletion<T>(this IEnumerable<Task<T>> tasks)
{
List<Task<T>> taskList = new List<Task<T>>(tasks);
while (taskList.Count > 0)
{
var finishedTask = await Task.WhenAny(taskList);
taskList.Remove(finishedTask);
yield return await finishedTask;
}
}

Correct way to a-synchronize parallel tasks

Currently we have this code which works fine:
Result result1 = null;
Result result2 = null;
var task1 = Task.Factory.StartNew(()=>
{
var records = DB.Read("..");
//Do A lot
result1 = Process(records);
});
var task2 = Task.Factory.StartNew(()=>
{
var records = DB.Read(".....");
//Do A lot
result2 = Process(records);
});
Task.WaitAll(task1, task2);
var result = Combine(result1, result2);
Now we would like to use async counterparts of DB Functions and we are using this new pattern:
Result result1 = null;
Result result2 = null;
var task1 = await Task.Factory.StartNew( async ()=>
{
var records = await DB.ReadAsync("..");
//Do A lot
result1 = Process(records);
});
var task2 = await Task.Factory.StartNew(async ()=>
{
var records = await DB.ReadAsync(".....");
//Do A lot
result2 = Process(records);
});
Task.WaitAll(task1, task2);
var result = Combine(result1, result2);
After we switched to async we started observing abnormal behavior. So I wonder if this is the correct pattern to parallelize async calls ?
Task.Factory.StartNew is a pre-async API. You should be using Task.Run which was designed with async-await in mind:
var task1 = await Task.Run( async ()=>
{
var records = await DB.ReadAsync("..");
//Do A lot
result1 = Process(records);
});
The issue is that an async lambda returns a Task so Task.Factory.StartNew returns a Task<Task> (the outer one because Task.Factory.StartNew returns a Task and the inner one which is the result of the async lambda).
This means that when you wait on task1 and task2 you aren't really waiting for the entire operation, just the synchronous part of it.
You can fix that by using Task.Unwrap on the returned Task<Task>:
Task<Task> task1 = await Task.Factory.StartNew(async ()=>
{
var records = await DB.ReadAsync("..");
//Do A lot
result1 = Process(records);
});
Task actualTask1 = task1.Unwrap();
await actualTask1;
But Task.Run does that implicitly for you.
As a side note, you should realize that you don't need Task.Run to execute these operations concurrently. You can do that just by calling these methods and awaiting the results together with Task.When:
async Task MainAsync()
{
var task1 = FooAsync();
var task2 = BarAsync();
await Task.WhenAll(task1, task2);
var result = Combine(task1.Result, task2.Result);
}
async Task<Result> FooAsync()
{
var records = await DB.ReadAsync("..");
//Do A lot
return Process(records);
}
async Task<Result> BarAsync()
{
var records = await DB.ReadAsync(".....");
//Do A lot
return Process(records);
}
You only need Task.Run if you need to offload even the synchronous parts of these methods (the part before the first await) to the ThreadPool.
Well using .WaitAll is not an async programming because you're actually blocking current thread on waiting. Also you dont call .Unwrap and that's why you just wait only on creating of async lambda, not on async lambda itself.
Task.Run can unwrap async lambda for you. But there's a simpler and cleaner way.
var task1 = DB.ReadAsync("..").ContinueWith(task => {
//Do A lot
return Process(task.Result);
}, TaskScheduler.Default);
var task2 = DB.ReadAsync("..").ContinueWith(task => {
//Do A lot
return Process(task.Result);
}, TaskScheduler.Default);
var result = Combine(await task1, await task2);
In this way you will get result exactly when it's ready. So you don't need additional tasks and variables at all.
Please note that ContinueWith is a tricky function and it works on TaskScheduler.Current if it is not null and otherwise it works on TaskScheduler.Default which is thread pool scheduler. So it's safer to specify scheduler explicitly always when calling this function.
Also for claryfing I didn't included error checking because actually DB.ReadAsync can be completed with an error. But that's an easy thing and you can handle it for yourself.
Task.Factory.StartNew start a new Task of execution another independent execution unit. So the simplest way to deal with that may look like:
var task1 = Task.Factory.StartNew(()=> //NO AWAIT
{
var records = DB.Read("....."); //NO ASYNC
//Do A lot
result1 = Process(records);
});
... another task definition
Task.WaitAll(task1, task2);
Read and process sequentially in one task, as you have data dependency.

How to process tasks as they complete -but each task requires different method to process task's result

I'm using async/await to call few external APIs. All of them returns me a string value but in different format and requires their own processing.
And I want to process the returned value as a task completes. I don't want to wait until all are completed and hence I'm using Task.WhenAny(). How can I process tasks as they complete and still use the correct "Process" method for each task as they complete?
I make some changes after my first post and here is the latest i have:
public async Task<List<string>> Get()
{
var task1 = Method1Async();
var task2 = Method1Async();
var tasks = new List<Task<string>> {task1, task2};
var results = new List<string>();
while (tasks.Count > 0)
{
var justCompletedTask = await Task.WhenAny(tasks);//will not throw
tasks.Remove(justCompletedTask);
try
{
var result = await justCompletedTask;
results.Add(result);
}
catch(Exception)
{
//deal with it
}
}
return results;
}
private async Task<string> Method1Async()
{
//this may throw - something like forbidden or any other exception
var task = _httpClient.GetStringAsync("api1's url here");
var result = await Method1ResultProcessorAsync(task);
return result;
}
private async Task<string> Method1ResultProcessorAsync(Task<string> task)
{
//process task's result -if it successuflly completed and return that
return await task; //for now
}
private async Task<string> Method2Async()
{
//this may throw - something like forbidden or any other exception
var task = _httpClient.GetStringAsync("api2's url here");
var result = await Method2ResultProcessorAsync(task);
return await task;
}
private async Task<string> Method2ResultProcessorAsync(Task<string> task)
{
//This processing logic is entirely different from Method1ResultProcessor
//process task's result -if it successfully completed and return that
return await task; //for now
}
I have two questions here:
Is this the right way to approach the problem?
How do i better handle exception here? This is very important so the failure of one should not fail the whole thing. As long as any of the methods succeed, it will be okay. But if all fails, I want to the Get method to throw.
Since your processor methods already accept Task, you can just call them and they will asynchronously wait for their corresponding results:
public Task<string[]> Get()
{
var task1 = Method1ResultProcessorAsync(Method1Async());
var task2 = Method2ResultProcessorAsync(Method2Async());
return Task.WhenAll(task1, task2);
}
Handling exceptions the way you describe will make this more complicated, but you can use something like:
public async Task<List<string>> Get()
{
var task1 = Method1ResultProcessorAsync(Method1Async());
var task2 = Method2ResultProcessorAsync(Method2Async());
var tasks = new[] { task1, task2 };
try
{
await Task.WhenAll(tasks);
}
catch {}
var results = tasks.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToList();
if (results.Any())
return results;
// or maybe another exception,
// since await handles AggregateException in a weird way
throw new AggregateException(tasks.Select(t => t.Exception));
}
Here is an alternative way of describing Method1Async() and Method2Async(). This is the demonstration of ContinueWith. Answering the question you ask in title – you do can use a different method for each task, that method will be called after the task completes.
var task1 = _httpClient.GetStringAsync("api1's url here").ContinueWith(t => Method1ResultProcessorAsync(t));
var task2 = _httpClient.GetStringAsync("api2's url here").ContinueWith(t => Method2ResultProcessorAsync(t));
You handle the exceptions correctly. Answering "But if all fails, I want to the Get method to throw.": just check whether results.Count == 0 before the return, and throw if it 0.

Categories

Resources