Why threads continue to run after a cancel has been called? - c#

Consider this simple example code:
var cts = new CancellationTokenSource();
var items = Enumerable.Range(1, 20);
var results = items.AsParallel().WithCancellation(cts.Token).Select(i =>
{
double result = Math.Log10(i);
return result;
});
try
{
foreach (var result in results)
{
if (result > 1)
cts.Cancel();
Console.WriteLine($"result = {result}");
}
}
catch (OperationCanceledException e)
{
if (cts.IsCancellationRequested)
Console.WriteLine($"Canceled");
}
Foreach of the results in the parallel results it prints the results until result > 1
This code output is something like:
result = 0.9030899869919435
result = 0.8450980400142568
result = 0.7781512503836436
result = 0
result = 0.6020599913279624
result = 0.47712125471966244
result = 0.3010299956639812
result = 0.6989700043360189
result = 0.9542425094393249
result = 1
result = 1.0413926851582251 <-- This is normal
result = 1.2041199826559248 <-- Why it prints this value (and below)
result = 1.0791812460476249
result = 1.2304489213782739
result = 1.1139433523068367
result = 1.255272505103306
result = 1.146128035678238
result = 1.2787536009528289
result = 1.1760912590556813
result = 1.3010299956639813
Canceled
My question is why it continue printing values over 1? I had expected that it the Cancel() token will be terminate the process.
Update 1
#mike-s's answer suggested:
It's also useful to check a cancellation token inside a loop (as a
means to abort the loop) or before a long operation.
I've tried adding a check
foreach (var result in results)
{
if (result > 1)
cts.Cancel();
if (!cts.IsCancellationRequested) //<----Check the cancellation token before printing
Console.WriteLine($"result = {result}");
}
It still gives the same result's output.

My question is why it continue printing values over 1?
Imagine you hired a hundred pilots to fly a hundred planes from a hundred airports. A bunch of them take off, and then you send a message saying "cancel all the flights". Well, there are a bunch of planes on the runway at takeoff speed when you send that message, and the message arrives after they are in the air. Those flights will not be cancelled!
You are discovering the most important thing to know about multithreaded programming. You have to reason as though every possible ordering of things happening might occur. That includes messages arriving later than you think they should.
In particular, your problem is a result of your abuse of the parallelization mechanisms, which are designed to parallelize long work. You've created a bunch of tasks that take less time to run than it takes to send the message stopping them. It should not be a surprise in that case that some of the tasks complete after they've been told to stop.
I expected that calling Cancel() on the token would terminate the process.
Your expectation is completely, totally wrong. Stop expecting that, since that expectation in no way conforms to reality. A cancellation token is a request to cancel an operation as soon as it is convenient to do so. It's not terminating a thread or a process.
However, even if you did terminate the threads, you would still observe this behaviour. Thread termination is an event like any other, and that event is not instantaneous. It takes time to execute, and other threads can continue their work while that thread termination is executing.
what do you mean by "convenient" in "a request to cancel an operation as soon as it is convenient to do so"?
Let's take a step back.
If the work to be done is extremely short, then there is no need to represent it as a task. Just do the work! In general if work takes less than about 30ms, just do the work.
Therefore, let's assume that every task takes a long time.
Now, why might a task take a long time? There are generally two reasons:
We're waiting for another system to complete some task. We're waiting for a network packet or a disk read or some such thing.
We have a huge amount of computation, and the CPU is saturated.
Suppose we are in the first situation. Does parallelizing help? No. If you are waiting for a package in the mail, hiring one, two, ten or a hundred people to wait does not make the package come faster.
But that does help for the second case; if we have an extra CPU in the machine we can dedicate two CPUs to solve the problem in about half the time.
Therefore we can assume that if we are parallelizing a task, it is because the CPU is doing a lot of work.
Great. Now, what is the nature of "CPU does a lot of work?" It almost always involves a loop somewhere.
So then, how do we cancel a task? We do not cancel a task by terminating the thread. We ask the task to cancel itself. A well-designed task will take a cancellation token, and in its loop will check to see if the cancellation token is indicating that the task is cancelled. Cancellation is cooperative. The task has to cooperate and decide when it checks to see if it is cancelled.
Notice that checking to see if you are cancelled is work, and that is work that takes time away from the real task. If you spend half your time checking to see if you are cancelled, your task takes twice as long as it could. And remember, the point of parallelizing the task is to make it take half as long, so doubling the amount of time it takes to do the task is a non-starter.
Therefore most tasks do not check every time through the loop if they are cancelled. A well-designed task will check every few milliseconds, not every few nanoseconds.
That's what I mean by "a cancellation is a request to stop when it is convenient". The task, if it was written correctly, should know what a good time to check for cancellation is so that it balances responsiveness against performance.

Cancel() on a cancellation token is just signaling the cancellation token, which just impacts other places in the code that check the token (such as calls to cts.IsCancellationRequested). Framework calls often will check the cancellation token and abort. It's also useful to check a cancellation token inside a loop (as a means to abort the loop) or before a long operation.
The cancellation token does not forcibly terminate a thread or process. There are other APIs for that, such as Environment.Exit.

Following up on Eric's excellent answer ... "a thread or process" and "a unit of work" usually should not be the same thing. Creating a thread to carry out one unit of work and then die is like shooting flaming arrows into the air: you can't control it, can't predict it, and those arrows start interfering with each other. The system becomes choked with so much work that it can't work on anything. (A condition called "thrashing.")
A much better strategy is modeled after a fast-food restaurant: a small number of workers, each with an assigned task, taking work-requests from a queue and delivering the finished sandwiches to another. At any instant, any queue might contain more or fewer entries. You don't see any of the workers falling down, dead. During lunch rush-hour, more workers are busy but at the same tasks. During a slow period they remain at their posts, patiently waiting for the next order to arrive. Any particular work-request might be flagged as "cancelled," and the workers notice this and respond accordingly. No part of the restaurant is "over-committed," and the entire operation is able to consistently produce a predictable number of sandwiches per hour, according to management control.

Related

Does SemaphoreSlim act as a gatekeeper or as a batchkeeper? [duplicate]

Here is the code I have but I don't understand what SemaphoreSlim is doing.
async Task WorkerMainAsync()
{
SemaphoreSlim ss = new SemaphoreSlim(10);
List<Task> trackedTasks = new List<Task>();
while (DoMore())
{
await ss.WaitAsync();
trackedTasks.Add(Task.Run(() =>
{
DoPollingThenWorkAsync();
ss.Release();
}));
}
await Task.WhenAll(trackedTasks);
}
void DoPollingThenWorkAsync()
{
var msg = Poll();
if (msg != null)
{
Thread.Sleep(2000); // process the long running CPU-bound job
}
}
What do await ss.WaitAsync(); and ss.Release(); do?
I guess that if I run 50 threads at a time then write code like SemaphoreSlim ss = new SemaphoreSlim(10); then it will be forced to run 10 active thread at time.
When one of 10 threads completes then another thread will start. If I am not right then help me to understand with sample situation.
Why is await needed along with ss.WaitAsync();? What does ss.WaitAsync(); do?
In the kindergarden around the corner they use a SemaphoreSlim to control how many kids can play in the PE room.
They painted on the floor, outside of the room, 5 pairs of footprints.
As the kids arrive, they leave their shoes on a free pair of footprints and enter the room.
Once they are done playing they come out, collect their shoes and "release" a slot for another kid.
If a kid arrives and there are no footprints left, they go play elsewhere or just stay around for a while and check every now and then (i.e., no FIFO priorities).
When a teacher is around, she "releases" an extra row of 5 footprints on the other side of the corridor such that 5 more kids can play in the room at the same time.
It also has the same "pitfalls" of SemaphoreSlim...
If a kid finishes playing and leaves the room without collecting the shoes (does not trigger the "release") then the slot remains blocked, even though there is theoretically an empty slot. The kid usually gets told off, though.
Sometimes one or two sneaky kid hide their shoes elsewhere and enter the room, even if all footprints are already taken (i.e., the SemaphoreSlim does not "really" control how many kids are in the room).
This does not usually end well, since the overcrowding of the room tends to end in kids crying and the teacher fully closing the room.
i guess that if i run 50 thread at a time then code like SemaphoreSlim ss = new SemaphoreSlim(10); will force to run 10 active thread at time
That is correct; the use of the semaphore ensures that there won't be more than 10 workers doing this work at the same time.
Calling WaitAsync on the semaphore produces a task that will be completed when that thread has been given "access" to that token. await-ing that task lets the program continue execution when it is "allowed" to do so. Having an asynchronous version, rather than calling Wait, is important both to ensure that the method stays asynchronous, rather than being synchronous, as well as deals with the fact that an async method can be executing code across several threads, due to the callbacks, and so the natural thread affinity with semaphores can be a problem.
A side note: DoPollingThenWorkAsync shouldn't have the Async postfix because it's not actually asynchronous, it's synchronous. Just call it DoPollingThenWork. It will reduce confusion for the readers.
Although I accept this question really relates to a countdown lock scenario, I thought it worth sharing this link I discovered for those wishing to use a SemaphoreSlim as a simple asynchronous lock. It allows you to use the using statement which could make coding neater and safer.
http://www.tomdupont.net/2016/03/how-to-release-semaphore-with-using.html
I did swap _isDisposed=true and _semaphore.Release() around in its Dispose though in case it somehow got called multiple times.
Also it is important to note SemaphoreSlim is not a reentrant lock, meaning if the same thread calls WaitAsync multiple times the count the semaphore has is decremented every time. In short SemaphoreSlim is not Thread aware.
Regarding the questions code-quality it is better to put the Release within the finally of a try-finally to ensure it always gets released.

When is the best place to use Task.Result instead of awaiting Task

Whilst I've been using async code in .NET for a while, I've only recently started to research it and understand what's going on. I've just been going through my code and trying to alter it so if a task can be done in parallel to some work, then it is. So for example:
var user = await _userRepo.GetByUsername(User.Identity.Name);
//Some minor work that doesn't rely on the user object
user = await _userRepo.UpdateLastAccessed(user, DateTime.Now);
return user;
Now becomes:
var userTask = _userRepo.GetByUsername(User.Identity.Name);
//Some work that doesn't rely on the user object
user = await _userRepo.UpdateLastAccessed(userTask.Result, DateTime.Now);
return user;
My understand is that the user object is now being fetched from the database WHILST some unrelated work is going on. However, things I've seen posted imply that result should be used rarely and await is preferred but I don't understand why I'd want to wait for my user object to be fetched if I can be performing some other independant logic at the same time?
Let's make sure to not bury the lede here:
So for example: [some correct code] becomes [some incorrect code]
NEVER NEVER NEVER DO THIS.
Your instinct that you can restructure your control flow to improve performance is excellent and correct. Using Result to do so is WRONG WRONG WRONG.
The correct way to rewrite your code is
var userTask = _userRepo.GetByUsername(User.Identity.Name);
//Some work that doesn't rely on the user object
user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now);
return user;
Remember, await does not make a call asynchronous. Await simply means "if the result of this task is not yet available, go do something else and come back here after it is available". The call is already asynchronous: it returns a task.
People seem to think that await has the semantics of a co-call; it does not. Rather, await is the extract operation on the task comonad; it is an operator on tasks, not call expressions. You normally see it on method calls simply because it is a common pattern to abstract away an async operation as a method. The returned task is the thing that is awaited, not the call.
However, things I've seen posted imply that result should be used rarely and await is preferred but I don't understand why I'd want to wait for my user object to be fetched if I can be performing some other independent logic at the same time?
Why do you believe that using Result will allow you to perform other independent logic at the same time??? Result prevents you from doing exactly that. Result is a synchronous wait. Your thread cannot be doing any other work while it is synchronously waiting for the task to complete. Use an asynchronous wait to improve efficiency. Remember, await simply means "this workflow cannot progress further until this task is completed, so if it is not complete, find more work to do and come back later". A too-early await can, as you note, make for an inefficient workflow because sometimes the workflow can progress even if the task is not complete.
By all means, move around where the awaits happen to improve efficiency of your workflow, but never never never change them into Result. You have some deep misunderstanding of how asynchronous workflows work if you believe that using Result will ever improve efficiency of parallelism in the workflow. Examine your beliefs and see if you can figure out which one is giving you this incorrect intuition.
The reason why you must never use Result like this is not just because it is inefficient to synchronously wait when you have an asynchronous workflow underway. It will eventually hang your process. Consider the following workflow:
task1 represents a job that will be scheduled to execute on this thread in the future and produce a result.
asynchronous function Foo awaits task1.
task1 is not yet complete, so Foo returns, allowing this thread to run more work. Foo returns a task representing its workflow, and signs up completing that task as the completion of task1.
The thread is now free to do work in the future, including task1.
task1 completes, triggering the execution of the completion of the workflow of Foo, and eventually completing the task representing the workflow of Foo.
Now suppose Foo instead fetches Result of task1. What happens? Foo synchronously waits for task1 to complete, which is waiting for the current thread to become available, which never happens because we're in a synchronous wait. Calling Result causes a thread to deadlock with itself if the task is somehow affinitized to the current thread. You can now make deadlocks involving no locks and only one thread! Don't do this.
Async await does not mean that several threads will be running your code.
However, it will lower the time your thread will be waiting idly for processes to finish, thus finishing earlier.
Whenever the thread normally would have to wait idly for something to finish, like waiting for a web page to download, a database query to finish, a disk write to finish, the async-await thread will not be waiting idly until the data is written / fetched, but looks around if it can do other things instead, and come back later after the awaitable task is finished.
This has been described with a cook analogy in this inverview with Eric Lippert. Search somewhere in the middle for async await.
Eric Lippert compares async-await with one(!) cook who has to make breakfast. After he starts toasting the bread he could wait idly until the bread is toasted before putting on the kettle for tea, wait until the water boils before putting the tea leaves in the teapot, etc.
An async-await cook, wouldn't wait for the toasted bread, but put on the kettle, and while the water is heating up he would put the tea leaves in the teapot.
Whenever the cook has to wait idly for something, he looks around to see if he can do something else instead.
A thread in an async function will do something similar. Because the function is async, you know there is somewhere an await in the function. In fact, if you forget to program the await, your compiler will warn you.
When your thread meets the await, it goes up its call stack to see if it can do something else, until it sees an await, goes up the call stack again, etc. Once everyone is waiting, he goes down the call stack and starts waiting idly until the first awaitable process is finished.
After the awaitable process is finished the thread will continue processing the statements after the await until he sees an await again.
It might be that another thread will continue processing the statements that come after the await (you can see this in the debugger by checking the thread ID). However this other thread has the context of the original thread, so it can act as if it was the original thread. No need for mutexes, semaphores, IsInvokeRequired (in winforms) etc. For you it seems as if there is one thread.
Sometimes your cook has to do something that takes up some time without idly waiting, like slicing tomatoes. In that case it might be wise to hire a different cook and order him to do the slicing. In the mean time your cook can continue with the eggs that just finished boiling and needed peeling.
In computer terms this would be if you had some big calculations without waiting for other processes. Note the difference with for instance writing data to disk. Once your thread has ordered that the data needs to be written to disk, it normally would wait idly until the data has been written. This is not the case when doing big calculations.
You can hire the extra cook using Task.Run
async Task<DateTime> CalculateSunSet()
{
// start fetching sunset data. however don't wait for the result yet
// you've got better things to do:
Task<SunsetData> taskFetchData = FetchSunsetData();
// because you are not awaiting your thread will do the following:
Location location = FetchLocation();
// now you need the sunset data, start awaiting for the Task:
SunsetData sunsetData = await taskFetchData;
// some big calculations are needed, that take 33 seconds,
// you want to keep your caller responsive, so start a Task
// this Task will be run by a different thread:
Task<DateTime> taskBigCalculations = Taks.Run( () => BigCalculations(sunsetData, location);
// again no await: you are still free to do other things
...
// before returning you need the result of the big calculations.
// wait until big calculations are finished, keep caller responsive:
DateTime result = await taskBigCalculations;
return result;
}
In your case, you can use:
user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now);
or perhaps more clearly:
var user = await _userRepo.GetByUsername(User.Identity.Name);
//Some work that doesn't rely on the user object
user = await _userRepo.UpdateLastAccessed(user, DateTime.Now);
The only time you should touch .Result is when you know the task has been completed. This can be useful in some scenarios where you are trying to avoid creating an async state machine and you think there's a good chance that the task has completed synchronously (perhaps using a local function for the async case), or if you're using callbacks rather than async/await, and you're inside the callback.
As an example of avoiding a state machine:
ValueTask<int> FetchAndProcess(SomeArgs args) {
async ValueTask<int> Awaited(ValueTask<int> task) => SomeOtherProcessing(await task);
var task = GetAsyncData(args);
if (!task.IsCompletedSuccessfully) return Awaited(task);
return new ValueTask<int>(SomeOtherProcessing(task.Result));
}
The point here is that if GetAsyncData returns a synchronously completed result, we completely avoid all the async machinery.
Have you considered this version?
var userTask = _userRepo.GetByUsername(User.Identity.Name);
//Some work that doesn't rely on the user object
user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now);
return user;
This will execute the "work" while the user is retrieved, but it also has all the advantages of await that are described in Await on a completed task same as task.Result?
As suggested you can also use a more explicit version to be able to inspect the result of the call in the debugger.
var userTask = _userRepo.GetByUsername(User.Identity.Name);
//Some work that doesn't rely on the user object
user = await userTask;
user = await _userRepo.UpdateLastAccessed(user, DateTime.Now);
return user;

What is the use case for async/await? [duplicate]

This question already has answers here:
Brief explanation of Async/Await in .Net 4.5
(3 answers)
Closed 7 years ago.
C# offers multiple ways to perform asynchronous execution such as threads, futures, and async.
In what cases is async the best choice?
I have read many articles about the how and what of async, but so far I have not seen any article that discusses the why.
Initially I thought async was a built-in mechanism to create a future. Something like
async int foo(){ return ..complex operation..; }
var x = await foo();
do_something_else();
bar(x);
Where call to 'await foo' would return immediately, and the use of 'x' would wait on the the return value of 'foo'. async does not do this. If you want this behavior you can use the futures library: https://msdn.microsoft.com/en-us/library/Ff963556.aspx
The above example would instead be something like
int foo(){ return ..complex operation..; }
var x = Task.Factory.StartNew<int>(() => foo());
do_something_else();
bar(x.Result);
Which isn't as pretty as I would have hoped, but it works nonetheless.
So if you have a problem where you want to have multiple threads operate on the work then use futures or one of the parallel operations, such as Parallel.For.
async/await, then, is probably not meant for the use case of performing work in parallel to increase throughput.
async solves the problem of scaling an application for a large number of asynchronous events, such as I/O, when creating many threads is expensive.
Imagine a web server where requests are processed immediately as they come in. The processing happens on a single thread where every function call is synchronous. To fully process a thread might take a few seconds, which means that an entire thread is consumed until the processing is complete.
A naive approach to server programming is to spawn a new thread for each request. In this way it does not matter how long each thread takes to complete because no thread will block any other. The problem with this approach is that threads are not cheap. The underlying operating system can only create so many threads before running out of memory, or some other kind of resource. A web server that uses 1 thread per request will probably not be able to scale past a few hundred/thousand requests per second. The c10k challenge asks that modern servers be able to scale to 10,000 simultaneous users. http://www.kegel.com/c10k.html
A better approach is to use a thread pool where the number of threads in existence is more or less fixed (or at least, does not expand past some tolerable maximum). In that scenario only a fixed number of threads are available for processing the incoming requests. If there are more requests than there are threads available for processing then some requests must wait. If a thread is processing a request and has to wait on a long running I/O process then effectively the thread is not being utilized to its fullest extent, and the server throughput will be much less than it otherwise could be.
The question is now, how can we have a fixed number of threads but still use them efficiently? One answer is to 'cut up' the program logic so that when a thread would normally wait on an I/O process, instead it will start the I/O process but immediately become free for any other task that wants to execute. The part of the program that was going to execute after the I/O will be stored in a thing that knows how to keep executing later on.
For example, the original synchronous code might look like
void process(){
string name = get_user_name();
string address = look_up_address(name);
string tax_forms = find_tax_form(address);
render_tax_form(name, address, tax_forms);
}
Where look_up_address and find_tax_form have to talk to a database and/or make requests to other websites.
The asynchronous version might look like
void process(){
string name = get_user_name();
invoke_after(() => look_up_address(name), (address) => {
invoke_after(() => find_tax_form(address), (tax_forms) => {
render_tax_form(name, address, tax_forms);
}
}
}
This is continuation passing style, where next thing to do is passed as the second lambda to a function that will not block the current thread when the blocking operation (in the first lambda) is invoked. This works but it quickly becomes very ugly and hard to follow the program logic.
What the programmer has manually done in splitting up their program can be automatically done by async/await. Any time there is a call to an I/O function the program can mark that function call with await to inform the caller of the program that it can continue to do other things instead of just waiting.
async void process(){
string name = get_user_name();
string address = await look_up_address(name);
string tax_forms = await find_tax_form(address);
render_tax_form(name, address, tax_forms);
}
The thread that executes process will break out of the function when it gets to look_up_address and continue to do other work: such as processing other requests. When look_up_address has completed and process is ready to continue, some thread (or the same thread) will pick up where the last thread left off and execute the next line find_tax_forms(address).
Since my current belief of async is about managing threads, I don't believe that async makes a lot of sense for UI programming. Generally UI's will not have that many simultaneous events that need to be processed. The use case for async with UI's is preventing the UI thread from being blocked. Even though async can be used with a UI, I would find it dangerous because ommitting an await on some long running function, due to either an accident or forgetfulness, would cause the UI to block.
async void button_callback(){
await do_something_long();
....
}
This code won't block the UI because it uses an await for the long running function that it invokes. If later on another function call is added
async void button_callback(){
do_another_thing();
await do_something_long();
...
}
Where it wasn't clear to the programmer who added the call to do_another_thing just how long it would take to execute, the UI will now be blocked. It seems safer to just always execute all processing in a background thread.
void button_callback(){
new Thread(){
do_another_thing();
do_something_long();
....
}.start();
}
Now there is no possibility that the UI thread will be blocked, and the chances that too many threads will be created is very small.

Delay and wake up a task from being executed? (Task.Delay?)

I have a situation where I have multiple threads being executed at once. In some cases these threads will be put in a while() loop for an unknown amount of time, and if a certain number of threads get caught in this loop then eventually the scheduler stops letting other threads be executed.
I was wondering if there is some way I could delay a thread from being executed (remove it from the scheduled list) and then let other threads in. Is it then possible to wake up that thread later by a threadID or something like that?
I am reading about Task.Delay and see it suspends execution from a timespan and that it is possible to time something out for an infinite amount of time, but is it possible to time it out indefinitely UNTIL a event occurs and then undelay it by some name or ID?
Edit: I thought this question was one that was harder to post code for, but more or less I have a situation where requests come in and are put into a loop like:
while(true){
//check for something that could make me want to delete this thread/request
//do some things
}
I had noticed that when I sent large number of requests that I never stopped ended up still in his loop (which I understand), but it seems the max amount of threads that could be doing this is 16/32 (depends on my computer that I run it on) and it is stopping other requests from being scheduled to run.
I wanted to know if inside the while() loop I could do something like this:
while(true){
//put this thread to sleep
//do some things that
//call some function to wake up the specific thread I need to do work on, before I put it back to sleep
}
The difference in this now is that instead of 16/32 threads running I can have 1 "king thread" that enters this while() loop that can 'do some things' and then wake up the thread that needs to be affected by the 'things'. Is there a way to sleep and wake up a specific thread so that other threads can be scheduled to run?
From the question I guess that you are running a busy waiting loop. That's pretty bad as you found out.
Make the loop wait for an event:
while (true) {
WaitForEvent();
DoWork();
}
This requires cooperation from the thread (or component) that makes the event happen. You could use a ManualResetEvent or a TaskCompletionSource to make this coordination happen.
I can't really be more specific because the question is not particularly concrete about the scenario. I hope this pushes you in the right direction.

Need to understand the usage of SemaphoreSlim

Here is the code I have but I don't understand what SemaphoreSlim is doing.
async Task WorkerMainAsync()
{
SemaphoreSlim ss = new SemaphoreSlim(10);
List<Task> trackedTasks = new List<Task>();
while (DoMore())
{
await ss.WaitAsync();
trackedTasks.Add(Task.Run(() =>
{
DoPollingThenWorkAsync();
ss.Release();
}));
}
await Task.WhenAll(trackedTasks);
}
void DoPollingThenWorkAsync()
{
var msg = Poll();
if (msg != null)
{
Thread.Sleep(2000); // process the long running CPU-bound job
}
}
What do await ss.WaitAsync(); and ss.Release(); do?
I guess that if I run 50 threads at a time then write code like SemaphoreSlim ss = new SemaphoreSlim(10); then it will be forced to run 10 active thread at time.
When one of 10 threads completes then another thread will start. If I am not right then help me to understand with sample situation.
Why is await needed along with ss.WaitAsync();? What does ss.WaitAsync(); do?
In the kindergarden around the corner they use a SemaphoreSlim to control how many kids can play in the PE room.
They painted on the floor, outside of the room, 5 pairs of footprints.
As the kids arrive, they leave their shoes on a free pair of footprints and enter the room.
Once they are done playing they come out, collect their shoes and "release" a slot for another kid.
If a kid arrives and there are no footprints left, they go play elsewhere or just stay around for a while and check every now and then (i.e., no FIFO priorities).
When a teacher is around, she "releases" an extra row of 5 footprints on the other side of the corridor such that 5 more kids can play in the room at the same time.
It also has the same "pitfalls" of SemaphoreSlim...
If a kid finishes playing and leaves the room without collecting the shoes (does not trigger the "release") then the slot remains blocked, even though there is theoretically an empty slot. The kid usually gets told off, though.
Sometimes one or two sneaky kid hide their shoes elsewhere and enter the room, even if all footprints are already taken (i.e., the SemaphoreSlim does not "really" control how many kids are in the room).
This does not usually end well, since the overcrowding of the room tends to end in kids crying and the teacher fully closing the room.
i guess that if i run 50 thread at a time then code like SemaphoreSlim ss = new SemaphoreSlim(10); will force to run 10 active thread at time
That is correct; the use of the semaphore ensures that there won't be more than 10 workers doing this work at the same time.
Calling WaitAsync on the semaphore produces a task that will be completed when that thread has been given "access" to that token. await-ing that task lets the program continue execution when it is "allowed" to do so. Having an asynchronous version, rather than calling Wait, is important both to ensure that the method stays asynchronous, rather than being synchronous, as well as deals with the fact that an async method can be executing code across several threads, due to the callbacks, and so the natural thread affinity with semaphores can be a problem.
A side note: DoPollingThenWorkAsync shouldn't have the Async postfix because it's not actually asynchronous, it's synchronous. Just call it DoPollingThenWork. It will reduce confusion for the readers.
Although I accept this question really relates to a countdown lock scenario, I thought it worth sharing this link I discovered for those wishing to use a SemaphoreSlim as a simple asynchronous lock. It allows you to use the using statement which could make coding neater and safer.
http://www.tomdupont.net/2016/03/how-to-release-semaphore-with-using.html
I did swap _isDisposed=true and _semaphore.Release() around in its Dispose though in case it somehow got called multiple times.
Also it is important to note SemaphoreSlim is not a reentrant lock, meaning if the same thread calls WaitAsync multiple times the count the semaphore has is decremented every time. In short SemaphoreSlim is not Thread aware.
Regarding the questions code-quality it is better to put the Release within the finally of a try-finally to ensure it always gets released.

Categories

Resources