How to stop all tasks when one is finished in c# - c#

I have different tasks to read from different files and find a word into them. I have put them into a task array which I start with waitAny method as following :
foreach (string file in filesList)
{
files[i] = Task.Factory.StartNew(() =>
{
mySearch.Invoke(file);
});
i++;
}
System.Threading.Tasks.Task.WaitAny(files);
I would like to stop all other tasks as soon as one of the tasks finishes (it finishes when it founds the word). For the moment, with waitany, i can know when one tasks finishes, but I don't know how I could know which one has finished and how to stop other tasks.
What would be the best way to achieve this ?

You can use single CancellationToken which all tasks will share. Inside mySearch.Invoke method verify value of token.IsCancellationRequested to cancel task. When some task will be finished cancel others via CancellationTokenSource.Cancel().
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
foreach (string file in filesList)
{
// pass cancellation token to your task
files[i] = Task.Factory.StartNew(() => mySearch.Invoke(file, token), token);
i++;
}
Task.WaitAny(files);
tokenSource.Cancel();
BTW you can force token to throw OperationCanceledException when source is canceled by calling token.ThrowIfCancellationRequested()

When creating a Task you can pass a CancelationToken. Set this token when one of the tasks finishes.
This will cause remaining tasks with this token to not execute. Running tasks can receive a OperationCanceledException and stop too.

I highly suggest reading How do I cancel non-cancelable async operations? by Stephen Toub. Essentially what you need to do is cancel all of these tasks, but currently you have no mechanism to cancel them.
The ideal approach would be to create a CancellationTokenSource before the foreach, pass the CancellationToken from that source to each of the child tasks, check that token periodically and stop doing work when you notice it's indicated cancellation. You can then cancel the token source in the WhenAny continuation.
If that's not an option you need to decide if it's important to actually stop the tasks (which, really, just can't be done) or if you just need to continue on with your code without waiting for them to finish (that's easy enough to do).

Related

High performance async monitoring tasks

I have a couple of hundred devices and I need to check their status every 5 seconds.
The API I'm using contains a blocking function that calls a dll and returns a status of a single device
string status = ReadStatus(int deviceID); // waits here until the status is returned
The above function usually returns the status in a couple of ms, but there will be situations where I might not get the status back for a second or more! Or even worse, one device might not respond at all.
I therefore need to introduce a form of asynchronicity to make sure that one device that doesn't respond doesn't impend all the others being monitored.
My current approach is as following
// triggers every 5 sec
public MonitorDevices_ElapsedInterval(object sender, ElapsedEventArgs elapsedEventArgs)
{
foreach (var device in lstDevices) // several hundred devices in the list
{
var task = device.ReadStatusAsync(device.ID, cts.Token);
tasks.Add(task);
}
// await all tasks finished, or timeout after 4900ms
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(4900, cts.Token));
cts.Cancel();
var devicesThatResponded = tasks.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.GetAwaiter().GetResult())
.ToList();
}
And below in the Device class
public async Task ReadStatusAsync(int deviceID, CancellationToken tk)
{
await Task.Delay(50, tk);
// calls the dll to return the status. Blocks until the status is return
Status = ReadStatus(deviceID);
}
I'm having several problems with my code
the foreach loops fires a couple of hundred tasks simultaneously, with the callback from the Task.Delay being served by a thread from the thread pool, each task taking a couple of ms.
I see this as a big potential bottleneck. Are there any better approaches?
This might be similar to what Stephen Cleary commented here, but he didn't provide an alternative What it costs to use Task.Delay()?
In case ReadStatus fails to return, I'm trying to use a cancellation token to cancel the thread that sits there waiting for the response... This doesn't seem to work.
await Task.Delay(50, tk)
Thread.Sleep(100000) // simulate the device not responding
I still have about 20 Worker Threads alive (even though I was expecting cts.Cancel() to kill them.
the foreach loops fires a couple of hundred tasks simultaneously
Since ReadStatus is synchronous (I'm assuming you can't change this), and since each one needs to be independent because they can block the calling thread, then you have to have hundreds of tasks. That's already the most efficient way.
Are there any better approaches?
If each device should be read every 5 seconds, then each device having its own timer would probably be better. After a few cycles, they should "even out".
await Task.Delay(50, tk);
I do not recommend using Task.Delay to "trampoline" non-async code. If you wish to run code on the thread pool, just wrap it in a Task.Run:
foreach (var device in lstDevices) // several hundred devices in the list
{
var task = Task.Run(() => device.ReadStatus(device.ID, cts.Token));
tasks.Add(task);
}
I'm trying to use a cancellation token to cancel the thread that sit there waiting for the response... This doesn't seem to work.
Cancellation tokens do not kill threads. If ReadStatus observes its cancellation token, then it should cancel; if not, then there isn't much you can do about it.
Thread pool threads should not be terminated; this reduces thread churn when the timer next fires.
As you can see in this Microsoft example page of a cancellation token, the doWork method is checking for cancellation on each loop. So, the loop has to start again to cancel out. In your case, when you simulate a long task, it never checks for cancellation at all when it's running.
From How do I cancel non-cancelable async operations?, it's saying at the end : "So, can you cancel non-cancelable operations? No. Can you cancel waits on non-cancelable operations? Sure… just be very careful when you do.". So it answers that we can't cancel it out.
What I would suggest is to use threads with a ThreadPool, you take the starting time of each one and you have an higher priority thread that looks if others bypass their maximum allowed time. If so, Thread.Interrupt().

Get thread id and terminate thread

I have an async method that can run multiple times in the same time. This method perform a call to a server so it takes some time to complete. I want each time an instance of this method is called to stop the execution of the same method that run in the same time. I was thinking about creating a list of thread id and everytime the method is called the threads that created for the execution of the older instances of this method will be terminated.
So what i am looking for is a way to get the current thread id that a specific method is running from, and the way to terminate a thread using its id.
PS. I've checked all posts about finding thread id etc but none of these work in UWP.
In UWP you don't have ability to control threads. But you can surely control tasks, and for example, cancel it if needed with with CancellationToken:
CancellationTokenSource tokenSource = new CancellationTokenSource();
await Task.Run(() =>
{
// your job here
}, tokenSource.Token);
// then if you want to cancel the job use tokenSource:
tokenSource.Cancel();
Note that this is only a simple example of using CancellationToken with Task.Run, but most of async methods uses these tokens and even you can convert them to task which will use one.
More about task cancellation you will find at MSDN, Stephen Cleary's blog and more.

Cancel task and wait for it to finish

I have a time consuming task which I need to run in a separate thread to avoid locking the GUI thread. As this task progresses, it updates a specific GUI control.
The catch is that the user might move to another part of the GUI before the task is over, and in that case, I have to:
Cancel the ongoing task (if it is active)
Wait till it's done cancelling: this is crucial, because the time consuming task's objective is to update a specific control. If more than one thread tries to do it at once, things might get messy.
Launch the task from scratch
For a concrete example, imagine the form has two parts: one where you navigate a directory tree, and another where you display thumbnails. When the user navigates to another directory, thumbnails need to be refreshed.
First I thought of using a BackgroundWorker and an AutoResetEvent to wait for cancellation, but I must have messed something because I got deadlocked when cancelling. Then I read about TPL, which is supposed to replace BGW and more primitive mechanisms.
Can this be done easily using TPL?
A few things to note:
You can get a CancellationToken from a CancellationTokenSource
Task cancellation is a cooperative action: if your task does not periodically check the CancellationToken.IsCancellationRequested property, it doesn't matter how many times you try to cancel the task, it will merrily churn away.
Those things said, here's the general idea:
void Main()
{
var tokenSource = new CancellationTokenSource();
var myTask = Task.Factory
.StartNew(() => DoWork(tokenSource.Token), tokenSource.Token);
Thread.Sleep(1000);
// ok, let's cancel it (well, let's "request it be cancelled")
tokenSource.Cancel();
// wait for the task to "finish"
myTask.Wait();
}
public void DoWork(CancellationToken token)
{
while(!token.IsCancellationRequested)
{
// Do useful stuff here
Console.WriteLine("Working!");
Thread.Sleep(100);
}
}

what is the correct way to cancel multiple tasks in c#

I have a button thats spawns 4 tasks. The same button changes to a cancel button and clicking this should cancel all 4 tasks. Should I pass the same cancel token to all 4 tasks and have them poll on the same token for IsCancelRequested ? I am confused after reading the msdn doc on createlinkedtokensource. How is this normally done ? thank you
Update: Task.WaitAll() waits tills all tasks complete execution. Similarly how to know when all tasks have been canceled once the shared cancel token source is set to cancel.
Yeah, what you said about using a single CancellationToken is correct. You can create a single CancellationTokenSource and use its CancellationToken for all of the tasks. Your tasks should check the token regularly for cancellation.
For example:
const int NUM_TASKS = 4;
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task[] tasks = new Task[NUM_TASKS];
for (int i = 0; i < NUM_TASKS; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(1000);
if (ct.IsCancellationRequested)
break;
}
}, ct);
}
Task.WaitAll(tasks);
Your button can call cts.Cancel(); to cancel the tasks.
Update for question update:
There are a few ways to do what you ask. One way is to use ct.IsCancellationRequested to check cancellation without throwing, then allow your task to complete. Then Task.WaitAll(tasks) will complete when all of the tasks have been cancelled.
I've updated the code to reflect that change.
Yes you should pass the same token and use this to cancel all the tasks in one go, if that's your intent.
Use BackroundWorker class, set property WorkerSupportsCancellation, start tasks by calling RunWorkerAsync() and stop them using CancelAsync()
You do not neet to sync your code with the UI.

Does Task.Wait(int) stop the task if the timeout elapses without the task finishing?

I have a task and I expect it to take under a second to run but if it takes longer than a few seconds I want to cancel the task.
For example:
Task t = new Task(() =>
{
while (true)
{
Thread.Sleep(500);
}
});
t.Start();
t.Wait(3000);
Notice that after 3000 milliseconds the wait expires. Was the task canceled when the timeout expired or is the task still running?
Task.Wait() waits up to specified period for task completion and returns whether the task completed in the specified amount of time (or earlier) or not. The task itself is not modified and does not rely on waiting.
Read nice series: Parallelism in .NET, Parallelism in .NET – Part 10, Cancellation in PLINQ and the Parallel class by Reed Copsey
And: .NET 4 Cancellation Framework / Parallel Programming: Task Cancellation
Check following code:
var cts = new CancellationTokenSource();
var newTask = Task.Factory.StartNew(state =>
{
var token = (CancellationToken)state;
while (!token.IsCancellationRequested)
{
}
token.ThrowIfCancellationRequested();
}, cts.Token, cts.Token);
if (!newTask.Wait(3000, cts.Token)) cts.Cancel();
If you want to cancel a Task, you should pass in a CancellationToken when you create the task. That will allow you to cancel the Task from the outside. You could tie cancellation to a timer if you want.
To create a Task with a Cancellation token see this example:
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var t = Task.Factory.StartNew(() => {
// do some work
if (token.IsCancellationRequested) {
// Clean up as needed here ....
}
token.ThrowIfCancellationRequested();
}, token);
To cancel the Task call Cancel() on the tokenSource.
The task is still running until you explicitly tell it to stop or your loop finishes (which will never happen).
You can check the return value of Wait to see this:
(from http://msdn.microsoft.com/en-us/library/dd235606.aspx)
Return Value
Type: System.Boolean
true if the Task completed execution within the allotted time; otherwise, false.
Was the task canceled when the timeout expired or is the task still running?
No and Yes.
The timeout passed to Task.Wait is for the Wait, not the task.
If your task calls any synchronous method that does any kind of I/O or other unspecified action that takes time, then there is no general way to "cancel" it.
Depending on how you try to "cancel" it, one of the following may happen:
The operation actually gets canceled and the resource it works on is in a stable state (You were lucky!)
The operation actually gets canceled and the resource it works on is in an inconsistent state (potentially causing all sorts of problems later)
The operation continues and potentially interferes with whatever your other code is doing (potentially causing all sorts of problems later)
The operation fails or causes your process to crash.
You don't know what happens, because it is undocumented
There are valid scenarios where you can and probably should cancel a task using one of the generic methods described in the other answers. But if you are here because you want to interrupt a specific synchronous method, better see the documentation of that method to find out if there is a way to interrupt it, if it has a "timeout" parameter, or if there is an interruptible variation of it.

Categories

Resources