CancellationTokenSource not working properly with Parallel.ForEach - c#

I have the following code:
CancellationTokenSource ts = new CancellationTokenSource(10000);
ParallelOptions po = new ParallelOptions();
po.CancellationToken = ts.Token;
List<int> lItems = new List<int>();
for (int i = 0; i < 20; i++)
lItems.Add(i);
System.Collections.Concurrent.ConcurrentBag<int> lBgs = new System.Collections.Concurrent.ConcurrentBag<int>();
Stopwatch sp = Stopwatch.StartNew();
try
{
Parallel.ForEach(lItems, po, i =>
{
Task.Delay(i * 1000).Wait();
lBgs.Add(i);
});
}
catch (Exception ex)
{
}
Console.WriteLine("Elapsed time: {0:N2} seg Total items: {1}", sp.ElapsedMilliseconds / 1000.0, lBgs.Count);
My question is why takes more than 20 sec to cancel the operation (parallel for) if the CancelationTokenSource is set to finish in 10 sec
Regards

Without a good Minimal, Complete, and Verifiable code example, it's impossible to fully understand your scenario. But based on the code you posted, it appears that you expect for your CancellationToken to affect the execution of each individual iteration of the Parallel.ForEach().
However, that's not how it works. The Parallel.ForEach() method schedules individual operations concurrently, but once those operations start, they are out of the control of the Parallel.ForEach() method. If you want them to terminate early, you have to do that yourself. E.g.:
Parallel.ForEach(lItems, po, i =>
{
Task.Delay(i * 1000, ts.Token).Wait();
lBgs.Add(i);
});
As your code stands now, all 20 actions are started almost immediately (there's a short delay as the thread pool creates enough threads for all the actions, if necessary), before you cancel the token. That is, by the time you cancel the token, the Parallel.ForEach() method no longer has a way to avoid starting the actions; they are already started!
Since your individual actions don't do anything to interrupt themselves, then all that's left is for them all to complete. The start-up time (including waiting for the thread pool to create enough worker threads), plus the longest total delay (i.e. the delay to start an action plus that action's delay), determines the total time the operation takes, with your cancellation token having no effect. Since your longest action is 20 seconds, the total delay for the Parallel.ForEach() operation will always be at least 20 seconds.
By making the change I show above, the delay task for each individual action will be cancelled by your token when it expires, causing a task-cancelled exception. This will cause the action itself to terminate early as well.
Note that there is still value in assigning the cancellation token to the ParallelOptions.CancellationToken property. Even though the cancellation happens too late to stop Parallel.ForEach() from starting all of the actions, by providing the token in the options, it can recognize that the exception thrown by each action was caused by the same cancellation token used in the options. With that, it then can throw just a single OperationCanceledException, instead of wrapping all of the action exceptions in an AggregateException.

I am assuming you are not actually
In response to
My question is why takes more than 20 sec to cancel the operation (parallel for) if the CancelationTokenSource is set to finish in 10 sec
This happens because you are not cancelling the Parallel.ForEach
In order to actually cancel you need to to use
po.CancellationToken.ThrowIfCancellationRequested();
inside the Parallel.ForEach code
As previous answer pointed out, if you want to actually cancel the task created by Task.Delay() you need to use the overload of Task.Delay which accepts a CancellationToken
Task.Delay(i * 1000, po.CancellationToken).Wait();
public static Task Delay(
TimeSpan delay,
CancellationToken cancellationToken
)
More details here
MSDN How to: Cancel a Parallel.For or ForEach Loop

Related

Why is semaphore not released when given cancellationToken is cancelled

SemaphoreSlim has a WaitAsync() method that takes a CancellationToken. I'd expect the semaphore to get released when that token is cancelled, but this doesn't seem to be the case. Consider the code below:
var tokenSource = new CancellationTokenSource();
var semaphore = new SemaphoreSlim(1, 1);
// CurrentCount is 1
await semaphore.WaitAsync(tokenSource.Token);
// CurrentCount is 0 as we'd expect
tokenSource.Cancel(); // Token is cancelled
for(var i = 0; i< 10; i++)
{
var c = semaphore.CurrentCount; // Current count remains 0, even though token was cancelled
await Task.Delay(1000);
}
Even after the token gets cancelled, the CurrentCount remains at 0. I'm guessing this is by design, but what exactly is the CancellationToken used for if not to release the semaphore when the token is cancelled? Thanks!
Background
SemaphoreSlim Class
Represents a lightweight alternative to Semaphore that limits the
number of threads that can access a resource or pool of resources
concurrently.
...
The count is decremented each time a thread enters the semaphore, and
incremented each time a thread releases the semaphore. To enter the
semaphore, a thread calls one of the Wait or WaitAsync overloads. To
release the semaphore, it calls one of the Release overloads. When the
count reaches zero, subsequent calls to one of the Wait methods block
until other threads release the semaphore. If multiple threads are
blocked, there is no guaranteed order, such as FIFO or LIFO, that
controls when threads enter the semaphore.
SemaphoreSlim.CurrentCount
Gets the number of remaining threads that can enter the SemaphoreSlim
object.
...
The initial value of the CurrentCount property is set by the call to
the SemaphoreSlim class constructor. It is decremented by each call to
the Wait or WaitAsync method, and incremented by each call to the
Release method.
That's to say, every time you enter a semaphore you reduce the remaining threads that can enter. If you have entered successfully, the count is decremented.
Calling the CancellationToken that is being awaited only has an effect on threads awaiting the WaitAsync, or if you try to await the token again.
To answer the question, the CancellationToken is solely for the awaiting WaitAsync, it has no affect on how many threads can enter the SemaphoreSlim.
Furthermore
I think the real and pertinent question is, do you need to release a SemephoreSlim that has been cancelled! and the answer is no. An awaiting SemephoreSlim has not successfully entered or affected the count anyway, it's awaiting because there are no threads permissible.
And lastly, do you need release a SemephoreSlim that has been timed out via the timeout overload. The answer to that is, this method returns a bool as to whether it was entered successfully, that return value needs to be checked to determined whether it may need to be released.
Fun fact
This is the exact reason why you don't put the wait inside a try finally pattern that releases the slim.
// this can throw, or timeout
var result = await semaphore.WaitAsync(someTimeOut, tokenSource.Token);
if(!result)
return;
try
{
// synchronized work here
}
finally
{
semaphore.Release();
}
Releasing inappropriately will cause all sorts of problems and result in another exception sooner or later because you are exceeding the maximum count.

How can I make sure my tests start and run correctly?

I'm determining between using TPL Dataflow blocks or some sort of producer/consumer approach for these tests. Producing a list of tasks will be super-fast, as each task will just be a string containing a list of test parameters such as the setup parameters, the measurements required, and time between measurements. This list of tasks will simply be files loaded through the GUI (1 file per test).
When at test is started, it should start right away. The tests could be very long and very asynchronous in that an action could take seconds or tens of minutes (e.g. heating up a device), followed by a measurement that takes a few seconds (or minutes), followed by a long period of inaction (24 hours) before the test is repeated again.
I could have up to 16 tests running at the same time, but I need the flexibility to be able to cancel any one of those tests at any time. I also need to be able to ADD a new test at any time (i.e. try to picture testing of 16 devices, or the span of a month in which individual test devices are added and removed throughout the month).
(Visual C#) I tried this example code for TPL dataflow where I tell it to run 32 simple tasks all at the same time. Each task is just a 5 second delay to simulate work. It appears to be processing the tasks in parallel as the time to complete the tasks took 15 seconds. I assume all 32 tasks did not finish in 5 seconds due to scheduling and any other overhead, but I am a bit worried that some task might of been blocked.
class Program
{
// Performs several computations by using dataflow and returns the elapsed
// time required to perform the computations.
static TimeSpan TimeDataflowComputations(int messageCount)
{
// Create an ActionBlock<int> that performs some work.
var workerBlock = new ActionBlock<int>(
// Simulate work by suspending the current thread.
millisecondsTimeout => Thread.Sleep(millisecondsTimeout),
// Specify a maximum degree of parallelism.
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = messageCount
});
// Compute the time that it takes for several messages to
// flow through the dataflow block.
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < messageCount; i++)
{
workerBlock.Post(5000); // simulated work: a delay of 5 seconds.
}
workerBlock.Complete();
// Wait for all messages to propagate through the network.
workerBlock.Completion.Wait();
// Stop the timer and return the elapsed number of milliseconds.
stopwatch.Stop();
return stopwatch.Elapsed;
}
static void Main(string[] args)
{
int messageCount = 32;
TimeSpan elapsed;
// set processors maximum degree of parallelism. This causes
// multiple messages to be processed in parallel.
Console.WriteLine("START:\r\n");
elapsed = TimeDataflowComputations(messageCount);
Console.WriteLine("message count = {1}; " +
"elapsed time = {2}ms.", messageCount,
(int)elapsed.TotalMilliseconds);
Console.ReadLine();
}
}
The demo seems to work, but I am not sure if any of the tasks were blocked until one or more of the 5 second tasks were completed. I am also not sure how one would go about identifying each action block in order to cancel a specific one.
The reason that you don't get the expected performance is because your workload is synchronous and blocks the thread-pool threads. Do you expect to actually have synchronous (blocking) workload in your production environment? If yes, you could try boosting the ThreadPool reserve of available threads before starting the TPL Dataflow pipeline:
ThreadPool.SetMinThreads(workerThreads: 100, completionPortThreads: 100);
If your actual workload is asynchronous, then you could better simulate it with Task.Delay instead of Thread.Sleep.
var workerBlock = new ActionBlock<int>(async millisecondsTimeout =>
{
await Task.Delay(millisecondsTimeout);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = messageCount
});
I didn't test it, but you should get completion times at around 5 sec with both these approaches.

Effects of non-awaited Task

I have a Task which I do not await because I want it to continue its own logic in the background. Part of that logic is to delay 60 seconds and check back in to see if some minute work is to be done. The abbreviate code looks something like this:
public Dictionary<string, Task> taskQueue = new Dictionary<string, Task>();
// Entry point
public void DoMainWork(string workId, XmlDocument workInstructions){
// A work task (i.e. "workInstructions") is actually a plugin which might use its own tasks internally or any other logic it sees fit.
var workTask = Task.Factory.StartNew(() => {
// Main work code that interprets workInstructions
// .........
// .........
// etc.
}, TaskCreationOptions.LongRunning);
// Add the work task to the queue of currently running tasks
taskQueue.Add(workId, workTask);
// Delay a period of time and then see if we need to extend our timeout for doing main work code
this.QueueCheckinOnWorkTask(workId); // Note the non-awaited task
}
private async Task QueueCheckinOnWorkTask(string workId){
DateTime startTime = DateTime.Now;
// Delay 60 seconds
await Task.Delay(60 * 1000).ConfigureAwait(false);
// Find out how long Task.Delay delayed for.
TimeSpan duration = DateTime.Now - startTime; // THIS SOMETIMES DENOTES TIMES MUCH LARGER THAN EXPECTED, I.E. 80+ SECONDS VS. 60
if(!taskQueue.ContainsKey(workId)){
// Do something based on work being complete
}else{
// Work is not complete, inform outside source we're still working
QueueCheckinOnWorkTask(workId); // Note the non-awaited task
}
}
Keep in mind, this is example code just to show a extremely miniminal version of what is going on with my actual program.
My problem is that Task.Delay() is delaying for longer than the time specified. Something is blocking this from continuing in a reasonable timeframe.
Unfortunately I haven't been able to replicate the issue on my development machine and it only happens on the server every couple of days. Lastly, it seems related to the number of work tasks we have running at a time.
What would cause this to delay longer than expected? Additionally, how might one go about debugging this type of situation?
This is a follow up to my other question which did not receive an answer: await Task.Delay() delaying for longer that expected
Most often that happens because of thread pool saturation. You can clearly see its effect with this simple console application (I measure time the same way you are doing, doesn't matter in this case if we use stopwatch or not):
public class Program {
public static void Main() {
for (int j = 0; j < 10; j++)
for (int i = 1; i < 10; i++) {
TestDelay(i * 1000);
}
Console.ReadKey();
}
static async Task TestDelay(int expected) {
var startTime = DateTime.Now;
await Task.Delay(expected).ConfigureAwait(false);
var actual = (int) (DateTime.Now - startTime).TotalMilliseconds;
ThreadPool.GetAvailableThreads(out int aw, out _);
ThreadPool.GetMaxThreads(out int mw, out _);
Console.WriteLine("Thread: {3}, Total threads in pool: {4}, Expected: {0}, Actual: {1}, Diff: {2}", expected, actual, actual - expected, Thread.CurrentThread.ManagedThreadId, mw - aw);
Thread.Sleep(5000);
}
}
This program starts 100 tasks which await Task.Delay for 1-10 seconds, and then use Thread.Sleep for 5 seconds to simulate work on a thread on which continuation runs (this is thread pool thread). It will also output total number of threads in thread pool, so you will see how it increases over time.
If you run it you will see that in almost all cases (except first 8) - actual time after delay is much longer than expected, in some cases 5 times longer (you delayed for 3 seconds but 15 seconds has passed).
That's not because Task.Delay is so imprecise. The reason is continuation after await should be executed on a thread pool thread. Thread pool will not always give you a thread when you request. It can consider that instead of creating new thread - it's better to wait for one of the current busy threads to finish its work. It will wait for a certain time and if no thread became free - it will still create a new thread. If you request 10 thread pool threads at once and none is free, it will wait for Xms and create new one. Now you have 9 requests in queue. Now it will again wait for Xms and create another one. Now you have 8 in queue, and so on. This wait for a thread pool thread to become free is what causes increased delay in this console application (and most likely in your real program) - we keep thread pool threads busy with long Thread.Sleep, and thread pool is saturated.
Some parameters of heuristics used by thread pool are available for you to control. Most influential one is "minumum" number of threads in a pool. Thread pool is expected to always create new thread without delay until total number of threads in a pool reaches configurable "minimum". After that, if you request a thread, it might either still create new one or wait for existing to become free.
So the most straightforward way to remove this delay is to increase minimum number of threads in a pool. For example if you do this:
ThreadPool.GetMinThreads(out int wt, out int ct);
ThreadPool.SetMinThreads(100, ct); // increase min worker threads to 100
All tasks in the example above will complete at the expected time with no additional delay.
This is usually not recommended way to solve this problem though. It's better to avoid performing long running heavy operations on thread pool threads, because thread pool is a global resource and doing this affects your whole application. For example, if we remove Thread.Sleep(5000) in the example above - all tasks will delay for expected amount of time, because all what keeps thread pool thread busy now is Console.WriteLine statement which completes in no time, making this thread available for other work.
So to sum up: identify places where you perform heavy work on thread pool threads and avoid doing that (perform heavy work on separate, non-thread-pool threads instead). Alternatively, you might consider increasing minimum number of threads in a pool to a reasonable amount.

Does overloaded version of Task.WaitAny Cancel the task?

int WaitAny(Task[] tasks, int millisecondsTimeout);
Does the above method cancel the task after the timeout period? It looks it doesn't but C# 70-483 exam reference book says this overloaded version cancel the task. Here is some test code,
Task longRunning = Task.Run(() =>
{
Thread.Sleep(10000);
});
int index = Task.WaitAny(new Task[] { longRunning }, 1000); // returns -1
index = 0; // reset it to reuse in the loop
while (index < 12)
{
Thread.Sleep(1000);
index++;
Console.WriteLine("long running task status {0}", longRunning.Status);
}
First few times, Status is Running and after that the status change to RanToCompletion. So what that time out does related to Task and WaitAny?
Task.WaitAny will complete if either (a) any of tasks it wraps complete or (b) the timeout is exceeded. If the timeout is exceeded, WaitAny completes, but the tasks it wraps will not be cancelled. Cancellation with tasks tends to be cooperative rather than implicit, and will involve the explicit passing of a CancellationToken.
If you want the task to cancel after a timeout you can create a CancellationTokenSource with a timeout and pass that into the task you're waiting for:
using(CancellationTokenSource cts=new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
var task = Task.Delay(TimeSpan.FromSeconds(10), cts.Token);
task.Wait();
}
Of course, waiting for tasks to complete using blocking methods is highly discouraged, as is wrapping synchronous blocking code in Task.Run and expecting everything to work correctly and "asynchronously".
Embrace async/await for the win.
If you look here: https://msdn.microsoft.com/en-us/library/dd235650(v=vs.110).aspx
IT says waitAny returns -1 if timeout has occurred. which is the result you are getting in the first place. Isn't that your expectation?
This means the code continues past WaitAny but does not mean that the tasks it was waiting for have been cancelled.
So what that time out does related to Task and WaitAny?
It does nothing. When you set time-out of 10000 it's like if you say:
"Hey Task, i give you ten seconds to finish, but then i'll keep going regardless of what's going on..."

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