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.
Related
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
I have following code:
public void Execute(IJobExecutionContext context)
{
lock (_lockObj)
{
// ... some HTTP job that can take 5 to 30 seconds ...
}
}
When an active job is working and a second thread enters the same method, I want it locked for second thread and wait.. However, when it is already locked for a second thread, and then a 3rd thread enters the code, I want it exit the method instead of waiting and entering job execution.
I could use a static counter variable and increase/decrease its value by the thread count within job execution. But I wonder if there is already a better practice to solve this issue.
You are looking for System.Threading.Semaphore.
var sem = new Semaphore(0, 2);
That creates a semaphore with an initial value of zero and a maximum of two. Up to two threads will be able to call sem.WaitOne() without blocking. After that, threads that call sem.WaitOne() will block until another thread calls sem.Release().
There is an overload WaitOne(int timeout) that accepts a timeout parameter. If zero is passed for the timeout, the call will not block, and return immediately. The boolean return value indicates whether or not you successfully acquired the semaphore. In your case, if it returns False, you simply abort the operation.
Use a Semaphore with a capacity of 2.
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().
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..."
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.