Using Task.WhenAll with a growing list of Tasks - c#

Task.WhenAll(IEnumerable<Task>) waits for all tasks in the IEnumerable are complete --- but only the tasks in the list when it's first called. If any active task adds to the list, they aren't considered. This short example demonstrates:
List<Task> _tasks = new List<Task>();
public async Task QuickExample()
{
for(int n =0; n < 6; ++n)
_tasks.Add(Func1(n));
await Task.WhenAll(_tasks);
Console.WriteLine("Some Tasks complete");
await Task.WhenAll(_tasks);
Console.WriteLine("All Tasks complete");
}
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
await Task.Delay(2000);
if ((n % 3) == 1)
_tasks.Add(Func2(n));
Console.WriteLine($"Func1-{n} complete");
}
async Task Func2(int n)
{
Console.WriteLine($"Func2-{n} started");
await Task.Delay(2000);
Console.WriteLine($"Func2-{n} complete");
}
This outputs:
Func1-0 started
Func1-1 started
Func1-2 started
Func1-3 started
Func1-4 started
Func1-5 started
Func1-5 complete
Func1-3 complete
Func2-1 started
Func1-1 complete
Func1-0 complete
Func1-2 complete
Func2-4 started
Func1-4 complete
Some Tasks complete
Func2-4 complete
Func2-1 complete
All Tasks complete
Done
The second Task.WhenAll() solves the problem in this case, but that's a rather fragile solution. What's the best way to handle this in the general case?

You are modifying the List<> without locking it... You like to live a dangerous life :-) Save the Count of the _tasks before doing a WaitAll, then after the WaitAll check the Count of _tasks. If it is different, do another round (so you need a while around the WaitAll.
int count = _tasks.Count;
while (true)
{
await Task.WhenAll(_tasks);
lock (_tasks)
{
if (count == _tasks.Count)
{
Console.WriteLine("All Tasks complete");
break;
}
count = _tasks.Count;
Console.WriteLine("Some Tasks complete");
}
}
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
await Task.Delay(2000);
if ((n % 3) == 1)
{
lock (_tasks)
{
_tasks.Add(Func2(n));
}
}
Console.WriteLine($"Func1-{n} complete");
}
I'll add a second (probably more correct solution), that is different from what you are doing: you could simply await the new Tasks from the Tasks that generated them, without cascading them to the _tasks collection. If A creates B, then A doesn't finish until B finishes. Clearly you don't need to add the new Tasks to the _tasks collection.

Asynchronous function will return to the caller on first await.
So for loop will be complete before you add extra tasks to original tasks list.
Implementation of Task.WhenAll will iterate/copy tasks to local list, so added tasks after Task.WhenAll called will be ignored.
In your particular case moving call to Func1 before await Task.Delay() could be a solution.
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
if ((n % 3) == 1)
_tasks.Add(Func2(n));
await Task.Delay(2000);
Console.WriteLine($"Func1-{n} complete");
}
But if in real scenario calling of Func2 depend on result of some asynchronous method, then you need some other solution.

Since it seems that additional tasks can be created during the course of executing the original list of tasks, you will need a simple while construct.
while (_tasks.Any( t => !t.IsCompleted )
{
await Task.WhenAll(_tasks);
}
This will check the list for any uncompleted tasks and await them until it catches the list at a moment when there are no tasks left.

Consider this; it sounds like work is being submitted to the "Task List" from another thread. In a sense, the "task submission" thread itself could also be yet another Task for you to wait on.
If you wait for all Tasks to be submitted, then you are guaranteed that your next call to WhenAll will yield a fully-completed payload.
Your waiting function could/should be a two-step process:
Wait for the "Task Submitting" task to complete, signalling all Tasks are submitted
Wait for all the submitted tasks to complete.
Example:
public async Task WaitForAllSubmittedTasks()
{
// Work is being submitted in a background thread;
// Wrap that thread in a Task, and wait for it to complete.
var workScheduler = GetWorkScheduler();
await workScheduler;
// All tasks submitted!
// Now we get the completed list of all submitted tasks.
// It's important to note: the submitted tasks
// have been chugging along all this time.
// By the time we get here, there are probably a number of
// completed tasks already. It does not delay the speed
// or execution of our work items if we grab the List
// after some of the work has been completed.
//
// It's entirely possible that - by the time we call
// this function and wait on it - almost all the
// tasks have already been completed!
var submittedWork = GetAllSubmittedTasks();
await Task.WhenAll(submittedWork);
// Work complete!
}

Related

Return data from long running Task on demand

I want to create a Task, which may run for many minutes, collecting data via an API call to another system. At some point in the future I need to stop the task and return the collected data. This future point is unknown at the time of starting the task.
I have read many question about returning data from tasks, but I can't find any that answer this scenario. I may be missing a trick, but all of the examples actually seem to wait in the man thread for the task to finish before continuing. This seems counter-intuitive, surely the purpose of a task is to hand off an activity whilst continuing with other activities in your main thread?
Here is one of those many examples, taken from DotNetPearls..
namespace TaskBasedAsynchronousProgramming
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Main Thread Started");
Task<double> task1 = Task.Run(() =>
{
return CalculateSum(10);
});
Console.WriteLine($"Sum is: {task1.Result}");
Console.WriteLine($"Main Thread Completed");
Console.ReadKey();
}
static double CalculateSum(int num)
{
double sum = 0;
for (int count = 1; count <= num; count++)
{
sum += count;
}
return sum;
}
}
}
Is it possible to do what I need, and have a long-running task running in parallel, stop it and return the data at an arbitrary future point?
Here is a sample application how you can do that:
static double partialResult = -1;
static void Main()
{
CancellationTokenSource calculationEndSignal = new(TimeSpan.FromSeconds(3));
Task meaningOfLife = Task.Run(() =>
GetTheMeaningOfLife(calculationEndSignal.Token),
calculationEndSignal.Token);
calculationEndSignal.Token.Register(() => Console.WriteLine(partialResult));
Console.ReadLine();
}
static async Task GetTheMeaningOfLife(CancellationToken cancellationToken)
{
foreach (var semiResult in Enumerable.Range(1, 42))
{
partialResult = semiResult;
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(1000);
}
}
partialResult is a shared variable between the two threads
The worker thread (GetTheMeaningOfLife) only writes it
The main thread (Main) only reads it
The read operation is performed only after the Task has been cancelled
calculationEndSignal is used to cancel the long-running operation
I've have specified a timeout, but you can call the Cancel method if you want
meaningOfLife is the Task which represents the long-running operation call
I have passed the CancellationToken to the GetTheMeaningOfLife and to the Task.Run as well
For this very simple example the Task.Run should not need to receive the token but it is generally a good practice to pass there as well
Register is receiving a callback which should be called after the token is cancelled
ReadLine can be any other computation
I've used ReadLine to keep the application running
GetTheMeaningOfLife simply increments the partialResult shared variable
either until it reaches the meaning of life
or until it is cancelled
Here is one approach. It features a CancellationTokenSource that is used as a stopping mechanism, instead of its normal usage as a cancellation mechanism. That's because you want to get the partial results, and a canceled Task does not propagate results:
CancellationTokenSource stoppingTokenSource = new();
Task<List<int>> longRunningTask = Task.Run(() =>
{
List<int> list = new();
for (int i = 1; i <= 60; i++)
{
if (stoppingTokenSource.IsCancellationRequested) break;
// Simulate a synchronous operation that has 1 second duration.
Thread.Sleep(1000);
list.Add(i);
}
return list;
});
Then, somewhere else in your program, you can send a stopping signal to the task, and then await asynchronously until the task acknowledges the signal and completes successfully. The await will also propagate the partial results:
stoppingTokenSource.Cancel();
List<int> partialResults = await longRunningTask;
Or, if you are not in an asynchronous workflow, you can wait synchronously until the partial results are available:
stoppingTokenSource.Cancel();
List<int> partialResults = longRunningTask.Result;

Calling UI Thread from Parallel Loop using Dispacher.BeginInvoke [duplicate]

This question already has answers here:
Parallel foreach with asynchronous lambda
(10 answers)
Closed 1 year ago.
I have a simple asynchronous code below : this is a WPF with one button and one textBox.
I used some list with five integers to mimic 5 different tasks.
My intention was to achieve that when I run all five tasks in parallel and asynchronously ,
I can observe that the numbers are one by one added to the textbox.
And I achieved it. Method "DoSomething" run all five tasks in parallel and each of the task has different execution time (simulated by Task.Delay) so all results in the numbers appearing in the textbox one by one.
The only problem that I cannot figure out is: why in the textbox I have the string text "This is end text" displayed at first ?! If I await method DoSomething then it should be accomplished first and then the rest of the code should be executed.Even though in my case is a repainting of the GUI.
I guess that this might be caused by the use of Dispacher.BeginInvoke which may "cause some disturbance " to async/await mechanism. But I would appreciate small clue and how to avoid this behawior.
I know that I could use the Progress event to achieve similar effect but is there any other way that I can use Parallel loop and update results progressively in WPF avoiding such a unexpected behaviour which I described?
private async void Button_Click(object sender, RoutedEventArgs e)
{
await DoSomething();
tbResults.Text += "This is end text";
}
private async Task DoSomething()
{
List<int> numbers = new List<int>(Enumerable.Range(1, 5));
await Task.Run(()=> Parallel.ForEach(numbers,async i =>
{
await Task.Delay(i * 300);
await Dispatcher.BeginInvoke(() => tbResults.Text += i.ToString() + Environment.NewLine);
}));
}
// output is:
//This is end text 1 2 3 4 5 (all in separatÄ™ lines).
My questions:
Why the text is displayed prior the method DoSomething .
How to solve it/ avoid it , any alternative way to solve it ( except using Progress event ).
Any info will be highly appreciate.
The threads of Parallel.Foreach are "real" background threads. They are created and the application continues execution. The point is that Parallel.Foreach is not awaitable, therefore the execution continues while the threads of the Parallel.Foreach are suspended using await.
private async Task DoSomething()
{
List<int> numbers = new List<int>(Enumerable.Range(1, 5));
// Create the threads of Parallel.Foreach
await Task.Run(() =>
{
// Create the threads of Parallel.Foreach and continue
Parallel.ForEach(numbers,async i =>
{
// await suspends the thread and forces to return.
// Because Parallel.ForEach is not awaitable,
// execution leaves the scope of the Parallel.Foreach to continue.
await Task.Delay(i * 300);
await Dispatcher.BeginInvoke(() => tbResults.Text += i.ToString() + Environment.NewLine);
});
// After the threads are created the internal await of the Parallel.Foreach suspends background threads and
// forces to the execution to return from the Parallel.Foreach.
// The Task.Run thread continues.
Dispatcher.InvokeAsync(() => tbResults.Text += "Text while Parallel.Foreach threads are suspended");
// Since the background threads of the Parallel.Foreach are not attached
// to the parent Task.Run, the Task.Run completes now and returns
// i.e. Task.run does not wait for child background threads to complete.
// ==> Leave Task.Run as there is no work.
});
// Leave DoSomething() and continue to execute the remaining code in the Button_Click().
// Parallel.Foreach threads is still suspended until the await chain, in this case Button_Click(), is completed.
}
The solution is to implement the pattern suggested by Clemens' comment or an async implementation of the Producer Consumer pattern using e.g., BlockingCollection or Channel to gain more control over the fixed number of threads while distributing the "unlimited" number of jobs.
private async Task DoSomething(int number)
{
await Task.Delay(number * 300);
Dispatcher.Invoke(() => tbResults.Text += number + Environment.NewLine);
}
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
List<int> numbers = new List<int>(Enumerable.Range(1, 5));
List<Task> tasks = new List<Task>();
// Alternatively use LINQ Select
foreach (int number in numbers)
{
Task task = DoSomething(number);
tasks.Add(task);
}
await Task.WhenAll(tasks);
tbResults.Text += "This is end text" + Environment.NewLine;
}
Discussing the comments
"my intention was to run tasks in parallel and "report" once they are
completed i.e. the taks which takes the shortest would "report" first
and so on."
This is exactly what is happening in the above solution. The Task with the shortest delay appends text to the TextBox first.
"But implementing your suggested await Task.WhenAll(tasks) causes
that we need to wait for all tasks to complete and then report all at
once."
To process Task objects in their order of completion, you would replace Task.WhenAll with Task.WhenAny. In case you are not only interested in the first completed Task, you would have to use Task.WhenAny in an iterative manner until all Task instances have been completed:
Process all Task objects in their order of completion
private async Task DoSomething(int number)
{
await Task.Delay(number * 300);
Dispatcher.Invoke(() => tbResults.Text += number + Environment.NewLine);
}
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
List<int> numbers = new List<int>(Enumerable.Range(1, 5));
List<Task> tasks = new List<Task>();
// Alternatively use LINQ Select
foreach (int number in numbers)
{
Task task = DoSomething(number);
tasks.Add(task);
}
// Until all Tasks have completed
while (tasks.Any())
{
Task<int> nextCompletedTask = await Task.WhenAny(tasks);
// Remove the completed Task so that
// we can await the next uncompleted Task that completes first
tasks.Remove(nextCompletedTask);
// Get the result of the completed Task
int taskId = await nextCompletedTask;
tbResults.Text += $"Task {taskId} has completed." + Environment.NewLine;
}
tbResults.Text += "This is end text" + Environment.NewLine;
}
"Parallel.ForEach is not awaitable so I thought that wrapping it up in
Task.Run allows me to await it but this is because as you said "Since
the background threads of the Parallel.Foreach are not attached to the
parent Task.Run""
No that's not exactly what I have said. The key point is the third sentence of my answer: "The point is that Parallel.Foreach is not awaitable, therefore the execution continues while the threads of the Parallel.Foreach are suspended using await.".
This means: normally Parallel.Foreach executes synchronously: the calling context continues execution when all threads of Parallel.Foreach have completed. But since you called await inside those threads, you suspend them in an async/await manner.
Since Parallel.Foreach is not awaitable, it can't handle the await calls and acts like the suspended threads have completed naturally. Parallel.Foreach does not understand that the threads are just suspended by await and will continue later. In other words, the await chain is broken as Parallel.Foreach is not able to return the Task to the parent awaited Task.Run context to signal its suspension.
That's what I meant when saying that the threads of Parallel.Foreach are not attached to the Task.Run. They run in total isolation from the async/await infrastructure.
"async lambdas should be "only use with events""
No, that's not correct. When you pass an async lambda to a void delegate like Action<T> you are correct: the async lambda can't be awaited in this case. But when passing an async lambda to a Func<T> delegate where T is of type Task, your lamda can be awaited:
private void NoAsyncDelegateSupportedMethod(Action lambda)
{
// Since Action does not return a Task (return type is always void),
// the async lambda can't be awaited
lambda.Invoke();
}
private async Task AsyncDelegateSupportedMethod(Func<Task> asyncLambda)
{
// Since Func returns a Task, the async lambda can be awaited
await asyncLambda.Invoke();
}
public voi DoSoemthing()
{
// Not a good idea as NoAsyncDelegateSupportedMethod can't handle async lamdas: it defines a void delegate
NoAsyncDelegateSupportedMethod(async () => await Task.Delay(1));
// A good idea as AsyncDelegateSupportedMethod can handle async lamdas: it defines a Func<Task> delegate
AsyncDelegateSupportedMethod(async () => await Task.Delay(1));
}
As you can see your statement is not correct. You must always check the signature of the called method and its overloads. If it accepts a Func<Task> type delegate you are good to go.
That's how async support is added to Parallel.ForeachAsync: the API supports a Func<ValueTask> type delegate. For example Task.Run accepts a Func<Task> and therefore the following call is perfectly fine:
Task.Run(async () => await Task.Delay(1));
" I guess that you admit that .Net 6.0 brought the best solution :
which is Parallel.ForEachASYNC! [...] We can spawn a couple of threads
which deal with our tasks in parallel and we can await the whole loop
and we do not need to wait for all tasks to complte- they "report" as
they finish "
That's wrong. Parallel.ForeachAsync supports threads thatr use async/await, that's true. Indeed, your original example would no longer break the intended flow: because Parallel.ForeachAsync supports await in its threads, it can handle suspended threads and propagate the Task object properly from its threads to the caller context e.g., to the wrapping await Task.Run.
It now knows how to wait for and resume suspended threads.
Important: Parallel.ForeachAsync still completes AFTER ALL its threads have completed. You assumption "they "report" as they finish" is wrong. That's the most intuitive concurrent implementation of a foreach. foreach also completes after all items are enumerated.
The solution to process Task objects as they complete is still using the Task.WhenAny pattern from above.
In general, if you you don't need the extra features like partitioning etc. of the the Parallel.Foreach and Parallel.ForeachAsync, you can always use Task.WhenAll instead. Task.WhenAll and especially Parallel.ForeachAsync are equivalent, except for Parallel.ForeachAsync provides greater customization by default: it suppors techniques like throttling and partitioning without the extra code.
Good links from Clemens see in comment. Answering your questions:
In Parallel.ForEach you start/fire for each entry of numbera an async task, which you don't await. So you do only await, that Parallel.ForEach does finish and it does finish before the async tasks of it.
What you could do e.g. remove async inside of Parallel.ForEach and use Dispatcher.Invoke instead of Dispatcher.BeginInvoke, Thread.Sleep is an anti-pattern ;) , so depending on your task may be take another solution(edited: BionicCode has a nice one):
private async Task DoSomething()
{
var numbers = new List<int>(Enumerable.Range(1, 5));
await Task.Run(()=> Parallel.ForEach(numbers, i =>
{
Thread.Sleep(i * 300);
Dispatcher.Invoke(() => tbResults.Text += i.ToString() + Environment.NewLine);
}));
}

How do I stop awaiting a task but keep the task running in background?

If I have a list of tasks which I want to execute together but at the same time I want to execute a certain number of them together, so I await for one of them until one of them finishes, which then should mean awaiting should stop and a new task should be allowed to start, but when one of them finishes, I don't know how to stop awaiting for the task which is currently being awaited, I don't want to cancel the task, just stop awaiting and let it continue running in the background.
I have the following code
foreach (var link in SharedVars.DownloadQueue)
{
if (currentRunningCount != batch)
{
var task = DownloadFile(extraPathPerLink, link, totalLen);
_ = task.ContinueWith(_ =>
{
downloadQueueLinksTasks.Remove(task);
currentRunningCount--;
// TODO SHOULD CHANGE WHAT IS AWAITED
});
currentRunningCount++;
downloadQueueLinksTasks.Add(task);
}
if (currentRunningCount == batch)
{
// TODO SHOULD NOT AWAIT 0
await downloadQueueLinksTasks[0];
}
}
I found about Task.WhenAny but from this comment here I understood that the other tasks will be ignored so it's not the solution I want to achieve.
I'm sorry if the question is stupid or wrong but I can't seem to find any information related on how to solve it, or what is the name of the operation I want to achieve so I can even search correctly.
Solution Edit
All the answers provided are correct, I accepted the one I decided to use but still all of them are correct.
Thank you everyone, I learned a lot from all of you from these different answers and different ways to approach the problem and how to think about it.
What I learned about this specific problem was that I still needed to await for the other tasks left, so the solution was to have the Task.WhenAny inside the loop (which returns the finished task (this is also important)) AND Task.WhenAll outside the loop to await the other left tasks.
Task.WhenAny returns the Task which completed.
foreach (var link in SharedVars.DownloadQueue)
{
var task = DownloadFile(extraPathPerLink, link, totalLen);
downloadQueueLinksTasks.Add(task);
if (downloadQueueLinksTasks.Count == batch)
{
// Wait for any Task to complete, then remove it from
// the list of pending tasks.
var completedTask = await Task.WhenAny(downloadQueueLinksTasks);
downloadQueueLinksTasks.Remove(completedTask);
}
}
// Wait for all of the remaining Tasks to complete
await Task.WhenAll(downloadQueueLinksTasks);
You can use Task.WaitAny()
Here is the demonstration of the behavior:
public static async Task Main(string[] args)
{
IList<Task> tasks = new List<Task>();
tasks.Add(TestAsync(0));
tasks.Add(TestAsync(1));
tasks.Add(TestAsync(2));
tasks.Add(TestAsync(3));
tasks.Add(TestAsync(4));
tasks.Add(TestAsync(5));
var result = Task.WaitAny(tasks.ToArray());
Console.WriteLine("returned task id is {0}", result);
///do other operations where
//before exiting wait for other tasks so that your tasks won't get cancellation signal
await Task.WhenAll(tasks.ToArray());
}
public static async Task TestAsync(int i)
{
Console.WriteLine("Staring to wait" + i);
await Task.Delay(new Random().Next(1000, 10000));
Console.WriteLine("Task finished" + i);
}
Output:
Staring to wait0
Staring to wait1
Staring to wait2
Staring to wait3
Staring to wait4
Staring to wait5
Task finished0
returned task id is 0
Task finished4
Task finished2
Task finished1
Task finished5
Task finished3
What you're asking about is throttling, which for asynchronous code is best expressed via SemaphoreSlim:
var semaphore = new SemaphoreSlim(batch);
var tasks = SharedVars.DownloadQueue.Select(link =>
{
await semaphore.WaitAsync();
try { return DownloadFile(extraPathPerLink, link, totalLen); }
finally { semaphore.Release(); }
});
var results = await Task.WhenAll(tasks);
You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:
IDisposable subscription =
SharedVars.DownloadQueue
.ToObservable()
.Select(link =>
Observable.FromAsync(() => DownloadFile(extraPathPerLink, link, totalLen)))
.Merge(batch) //max concurrent downloads
.Subscribe(file =>
{
/* process downloaded file here
(no longer a task) */
});
If you need to stop the downloads before they would naturally finish just call subscription.Dispose().

WaitAll for Changing List<Task>

Updated to explain things more clearly
I've got an application that runs a number of tasks. Some are created initially and other can be added later. I need need a programming structure that will wait on all the tasks to complete. Once the all the tasks complete some other code should run that cleans things up and does some final processing of data generated by the other tasks.
I've come up with a way to do this, but wouldn't call it elegant. So I'm looking to see if there is a better way.
What I do is keep a list of the tasks in a ConcurrentBag (a thread safe collection). At the start of the process I create and add some tasks to the ConcurrentBag. As the process does its thing if a new task is created that also needs to finish before the final steps I also add it to the ConcurrentBag.
Task.Wait accepts an array of Tasks as its argument. I can convert the ConcurrentBag into an array, but that array won't include any Tasks added to the Bag after Task.Wait was called.
So I have a two step wait process in a do while loop. In the body of the loop I do a simple Task.Wait on the array generated from the Bag. When it completes it means all the original tasks are done. Then in the while test I do a quick 1 millisecond test of a new array generated from the ConcurrentBag. If no new tasks were added, or any new tasks also completed it will return true, so the not condition exits the loop.
If it returns false (because a new task was added that didn't complete) we go back and do a non-timed Task.Wait. Then rinse and repeat until all new and old tasks are done.
// defined on the class, perhaps they should be properties
CancellationTokenSource Source = new CancellationTokenSource();
CancellationToken Token = Source.Token;
ConcurrentBag<Task> ToDoList = new ConcurrentBag<Task>();
public void RunAndWait() {
// start some tasks add them to the list
for (int i = 0; i < 12; i++)
{
Task task = new Task(() => SillyExample(Token), Token);
ToDoList.Add(task);
task.Start();
}
// now wait for those task, and any other tasks added to ToDoList to complete
try
{
do
{
Task.WaitAll(ToDoList.ToArray(), Token);
} while (! Task.WaitAll(ToDoList.ToArray(), 1, Token));
}
catch (OperationCanceledException e)
{
// any special handling of cancel we might want to do
}
// code that should only run after all tasks complete
}
Is there a more elegant way to do this?
I'd recommend using a ConcurrentQueue and removing items as you wait for them. Due to the first-in-first-out nature of queues, if you get to the point where there's nothing left in the queue, you know that you've waited for all the tasks that have been added up to that point.
ConcurrentQueue<Task> ToDoQueue = new ConcurrentQueue<Task>();
...
while(ToDoQueue.Count > 0 && !Token.IsCancellationRequested)
{
Task task;
if(ToDoQueue.TryDequeue(out task))
{
task.Wait(Token);
}
}
Here's a very cool way using Microsoft's Reactive Framework (NuGet "Rx-Main").
var taskSubject = new Subject<Task>();
var query = taskSubject.Select(t => Observable.FromAsync(() => t)).Merge();
var subscription =
query.Subscribe(
u => { /* Each Task Completed */ },
() => Console.WriteLine("All Tasks Completed."));
Now, to add tasks, just do this:
taskSubject.OnNext(Task.Run(() => { }));
taskSubject.OnNext(Task.Run(() => { }));
taskSubject.OnNext(Task.Run(() => { }));
And then to signal completion:
taskSubject.OnCompleted();
It is important to note that signalling completion doesn't complete the query immediately, it will wait for all of the tasks to finish too. Signalling completion just says that you will no longer add any new tasks.
Finally, if you want to cancel, then just do this:
subscription.Dispose();

How to determine when all task is completed

here is sample code for starting multiple task
Task.Factory.StartNew(() =>
{
//foreach (KeyValuePair<string, string> entry in dicList)
Parallel.ForEach(dicList,
entry =>
{
//create and add the Progress in UI thread
var ucProgress = (Progress)fpPanel.Invoke(createProgress, entry);
//execute ucProgress.Process(); in non-UI thread in parallel.
//the .Process(); must update UI by using *Invoke
ucProgress.Process();
System.Threading.Thread.SpinWait(5000000);
});
});
.ContinueWith(task =>
{
//to handle exceptions use task.Exception member
var progressBar = (ProgressBar)task.AsyncState;
if (!task.IsCancelled)
{
//hide progress bar here and reset pb.Value = 0
}
},
TaskScheduler.FromCurrentSynchronizationContext() //update UI from UI thread
);
when we start multiple task using Task.Factory.StartNew() then we can use .ContinueWith() block to determine when each task finish. i mean ContinueWith block fire once for each task completion. so i just want to know is there any mechanism in TPL library. if i start 10 task using Task.Factory.StartNew() so how do i notify after when 10 task will be finish. please give some insight with sample code.
if i start 10 task using Task.Factory.StartNew() so how do i notify after when 10 task will be finish
Three options:
The blocking Task.WaitAll call, which only returns when all the given tasks have completed
The async Task.WhenAll call, which returns a task which completes when all the given tasks have completed. (Introduced in .NET 4.5.)
TaskFactory.ContinueWhenAll, which adds a continuation task which will run when all the given tasks have completed.
if i start 10 task using Task.Factory.StartNew() so how do i notify after when 10 task will be finish
You can use Task.WaitAll. This call will block current thread until all tasks are finished.
Side note: you seem to be using Task, Parallel and Thread.SpinWait, which makes your code complex. I would spend a bit of time analysing if that complexity is really necessary.
You can use the WaitAll(). Example :
Func<bool> DummyMethod = () =>{
// When ready, send back complete!
return true;
};
// Create list of tasks
System.Threading.Tasks.Task<bool>[] tasks = new System.Threading.Tasks.Task<bool>[2];
// First task
var firstTask = System.Threading.Tasks.Task.Factory.StartNew(() => DummyMethod(), TaskCreationOptions.LongRunning);
tasks[0] = firstTask;
// Second task
var secondTask = System.Threading.Tasks.Task.Factory.StartNew(() => DummyMethod(), TaskCreationOptions.LongRunning);
tasks[1] = secondTask;
// Launch all
System.Threading.Tasks.Task.WaitAll(tasks);
Another solution:
After the completion of all the operation inside Parallel.For(...) it return an onject of ParallelLoopResult, Documentation:
For returns a System.Threading.Tasks.ParallelLoopResult object when
all threads have completed. This return value is useful when you are
stopping or breaking loop iteration manually, because the
ParallelLoopResult stores information such as the last iteration that
ran to completion. If one or more exceptions occur on one of the
threads, a System.AggregateException will be thrown.
The ParallelLoopResult class has a IsCompleted property that is set to false when a Stop() of Break() method has been executed.
Example:
ParallelLoopResult result = Parallel.For(...);
if (result.IsCompleted)
{
//Start another task
}
Note that it advised to use it only when breaking or stoping the loop manually (otherwise just use WaitAll, WhenAll etc).

Categories

Resources