How does asynchronous programming work with threads when using Thread.Sleep()? - c#

Presumptions/Prelude:
In previous questions, we note that Thread.Sleep blocks threads see: When to use Task.Delay, when to use Thread.Sleep?.
We also note that console apps have three threads: The main thread, the GC thread & the finalizer thread IIRC. All other threads are debugger threads.
We know that async does not spin up new threads, and it instead runs on the synchronization context, "uses time on the thread only when the method is active". https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model
Setup:
In a sample console app, we can see that neither the sibling nor the parent code are affected by a call to Thread.Sleep, at least until the await is called (unknown if further).
var sw = new Stopwatch();
sw.Start();
Console.WriteLine($"{sw.Elapsed}");
var asyncTests = new AsyncTests();
var go1 = asyncTests.WriteWithSleep();
var go2 = asyncTests.WriteWithoutSleep();
await go1;
await go2;
sw.Stop();
Console.WriteLine($"{sw.Elapsed}");
Stopwatch sw1 = new Stopwatch();
public async Task WriteWithSleep()
{
sw1.Start();
await Task.Delay(1000);
Console.WriteLine("Delayed 1 seconds");
Console.WriteLine($"{sw1.Elapsed}");
Thread.Sleep(9000);
Console.WriteLine("Delayed 10 seconds");
Console.WriteLine($"{sw1.Elapsed}");
sw1.Stop();
}
public async Task WriteWithoutSleep()
{
await Task.Delay(3000);
Console.WriteLine("Delayed 3 second.");
Console.WriteLine($"{sw1.Elapsed}");
await Task.Delay(6000);
Console.WriteLine("Delayed 9 seconds.");
Console.WriteLine($"{sw1.Elapsed}");
}
Question:
If the thread is blocked from execution during Thread.Sleep, how is it that it continues to process the parent and sibling? Some answer that it is background threads, but I see no evidence of multithreading background threads. What am I missing?

I see no evidence of multithreading background threads. What am I missing?
Possibly you are looking in the wrong place, or using the wrong tools. There's a handy property that might be of use to you, in the form of Thread.CurrentThread.ManagedThreadId. According to the docs,
A thread's ManagedThreadId property value serves to uniquely identify that thread within its process.
The value of the ManagedThreadId property does not vary over time
This means that all code running on the same thread will always see the same ManagedThreadId value. If you sprinkle some extra WriteLines into your code, you'll be able to see that your tasks may run on several different threads during their lifetimes. It is even entirely possible for some async applications to have all their tasks run on the same thread, though you probably won't see that behaviour in your code under normal circumstances.
Here's some example output from my machine, not guaranteed to be the same on yours, nor is it necessarily going to be the same output on successive runs of the same application.
00:00:00.0000030
* WriteWithSleep on thread 1 before await
* WriteWithoutSleep on thread 1 before first await
* WriteWithSleep on thread 4 after await
Delayed 1 seconds
00:00:01.0203244
* WriteWithoutSleep on thread 5 after first await
Delayed 3 second.
00:00:03.0310891
* WriteWithoutSleep on thread 6 after second await
Delayed 9 seconds.
00:00:09.0609263
Delayed 10 seconds
00:00:10.0257838
00:00:10.0898976
The business of running tasks on threads is handled by a TaskScheduler. You could write one that forces code to be single threaded, but that's not often a useful thing to do. The default scheduler uses a threadpool, and as such tasks can be run on a number of different threads.

The Task.Delay method is implemented basically like this (simplified¹):
public static Task Delay(int millisecondsDelay)
{
var tcs = new TaskCompletionSource();
_ = new Timer(_ => tcs.SetResult(), null, millisecondsDelay, -1);
return tcs.Task;
}
The Task is completed on the callback of a System.Threading.Timer component, and according to the documentation this callback is invoked on a ThreadPool thread:
The method does not execute on the thread that created the timer; it executes on a ThreadPool thread supplied by the system.
So when you await the task returned by the Task.Delay method, the continuation after the await runs on the ThreadPool. The ThreadPool typically has more than one threads available immediately on demand, so it's not difficult to introduce concurrency and parallelism if you create 2 tasks at once, like you do in your example. The main thread of a console application is not equipped with a SynchronizationContext by default, so there is no mechanism in place to prevent the observed concurrency.
¹ For demonstration purposes only. The Timer reference is not stored anywhere, so it might be garbage collected before the callback is invoked, resulting in the Task never completing.

I am not accepting my own answer, I will accept someone else's answer because they helped me figure this out. First, in the context of my question, I was using async Main. It was very hard to choose between Theodor's & Rook's answer. However, Rook's answer provided me with one thing that helped me fish: Thread.CurrentThread.ManagedThreadId
These are the results of my running code:
1 00:00:00.0000767
Not Delayed.
1 00:00:00.2988809
Delayed 1 second.
4 00:00:01.3392148
Delayed 3 second.
5 00:00:03.3716776
Delayed 9 seconds.
5 00:00:09.3838139
Delayed 10 seconds
4 00:00:10.3411050
4 00:00:10.5313519
I notice that there are 3 threads here, The initial thread (1) provides for the first calling method and part of the WriteWithSleep() until Task.Delay is initialized and later awaited. At the point that Task.Delay is brought back into Thread 1, everything is run on Thread 4 instead of Thread 1 for the main and the remainder of WriteWithSleep.
WriteWithoutSleep uses its own Thread(5).
So my error was believing that there were only 3 threads. I believed the answer to this question: https://stackoverflow.com/questions/3476642/why-does-this-simple-net-console-app-have-so-many-threads#:~:text=You%20should%20only%20see%20three,see%20are%20debugger%2Drelated%20threads.
However, that question may not have been async, or may not have considered these additional worker threads from the threadpool.
Thank you all for your assistance in figuring out this question.

Related

Differences between Multithreading and Async

Note: Please read to the end before marking as duplicate. I've read the other answers, and they don't seem to answer my question.
I've seen various pictures and people point out and say that multithreading is different from asynchronous programming, by giving various analogies to restaurant workers and the like. But I've yet to see the difference with an actual example.
I tried this in C#:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTest
{
class Program
{
static void RunSeconds(double seconds)
{
int ms = (int)(seconds * 1000);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine($"Thread started to run for {seconds} seconds");
Thread.Sleep(ms);
stopwatch.Stop();
Console.WriteLine($"Stopwatch passed {stopwatch.ElapsedMilliseconds} ms.");
}
static async Task RunSecondsAsync(double seconds)
{
int ms = (int)(seconds * 1000);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine($"Thread started to run for {seconds} seconds");
await Task.Run(() => Thread.Sleep(ms));
stopwatch.Stop();
Console.WriteLine($"Stopwatch passed {stopwatch.ElapsedMilliseconds} ms.");
}
static void RunSecondsThreaded(double seconds)
{
Thread th = new Thread(() => RunSeconds(seconds));
th.Start();
}
static async Task Main()
{
Console.WriteLine("Synchronous:");
RunSeconds(2.5); RunSeconds(2);
Console.WriteLine("\nAsynchronous:");
Task t1 = RunSecondsAsync(2.5); Task t2 = RunSecondsAsync(2);
await t1; await t2;
Console.WriteLine("\nMultithreading:");
RunSecondsThreaded(2.5); RunSecondsThreaded(2);
}
}
}
Results:
Synchronous:
Thread started to run for 2.5 seconds
Stopwatch passed 2507 ms.
Thread started to run for 2 seconds
Stopwatch passed 2001 ms.
Asynchronous:
Thread started to run for 2.5 seconds
Thread started to run for 2 seconds
Stopwatch passed 2002 ms.
Stopwatch passed 2554 ms.
Multithreading:
Thread started to run for 2.5 seconds
Thread started to run for 2 seconds
Stopwatch passed 2000 ms.
Stopwatch passed 2501 ms.
They yielded essentially the same results, behaviour-wise. So when and what exactly would I find different in the behaviour of a multithreaded program vs an asynchronous one?
I have various other issues to resolve:
In this image, for example:
What I don't get is that when you run an asynchronous program, it behaves practically identically to a multithreaded one, in that it seems to spend a similar amount of time. By the image above, it's addressing the asynchronous task in "breaks". If it does this, shouldn't it take longer for the asynchronous task to complete?
Let's say an asynchronous task which would normally complete 3 seconds synchronously while locking other tasks is run, should I not expect these tasks to finish in much longer than 3 seconds, given that it does other tasks on the side while taking breaks from my original task?
So why does it often take a similar asynchronously (ie. the usual 3 seconds)? And why does the program become "responsive": if the task is not being done on a separate thread, why does working on the task while working on other tasks on the side take only the expected 3 seconds?
The problem I have with the examples using workers in a restaurant (see top answer), is that in a restaurant, the cooking is done by the oven. In a computer, this analogy doesn't make much sense, as it's not clear why the oven isn't being treated as a separate "thread" but the people/workers are.
Furthermore, does a multithreaded application use more memory? And if it does, is it possible to create a simple application (ideally as similar to the one above) proving that it does?
Bit of a lengthy question, but the differences between multithreading and asynchronous programming are far from clear to me.
You can't use Thread.Sleep in async code, use
await Task.Delay(1000);
instead.
The async code uses a thread pool, any time the program awaits for some IO to complete, the thread is returned to the pool to do other stuff. Once the IO completes, the async method resumes at the line where it yielded the thread back to threadpool, continuing on.
When you manipulate with the Thread directly, you block and your code is no longer async, you also starve the threadpool as it is limited in the number of threads available.
Also throughout the lifetime of an async method, you are not guaranteed every line will be executed on the same thread. Generally after every await keyword the thread may change.
You never want to touch the Thread class in an async method.
By doing:
await Task.Run(() => Thread.Sleep(ms));
You force the TPL to allocate a thread out of the pool to block it, starving it.
By doing
await Task.Run(async () => await Task.Delay(ms));
you will essentially run on one or two threads from a pool even if you start it many times.
Running Task.Run() on synchronous code is mostly used for legacy calls that do not support async internally and the TPL just wraps the sync call in a pooled thread. To get the full advantages of async code you need to await a call that itself runs only async code internally.
Let me try to correlate your program with a real world example and then explain it.
Consider your program to be an IT office and your are the boss of it. Boss means the main thread which starts the program execution. The console can be considered as your diary.
Programs execution starts:
static async Task Main()
{
Process process = Process.GetCurrentProcess();
Console.WriteLine("Synchronous:");
You enter into the office from the main door and log "Synchronous:" into your diary.
Synchronous:
Calling method 'RunSeconds()'
RunSeconds(2.5); RunSeconds(2);
Let us assume 'RunSeconds()' is equivalent to a call from one of your projects client, however there is no one to attend the calls. So you attend both the calls.The thing to remember is you attend the calls one after the other as you are one person and total spent is close to 4.5 seconds.
Meanwhile you get a call from your home but you could not attend it because you were busy attending the client calls. Now coming to logging of the calls.You get a call you log it.Once it is completed you log the amount of time spent on call. And you do it twice for both the calls.
Thread started to run for 2.5 seconds
Stopwatch passed 2507 ms.
Thread started to run for 2 seconds
Stopwatch passed 2001 ms.
Console.WriteLine("\nAsynchronous:");
Then you log "Asynchronous:" into the diary
Calling method 'RunSecondsAsync()'
Task t1 = RunSecondsAsync(2.5); Task t2 = RunSecondsAsync(2);
await t1; await t2;
Let us assume 'RunSecondsAsync()' is again equivalent to a call from one of your projects client, however this time you have a Manager with a team of 10 call attendants who take the call. Here Manager is equivalent to the Task and each call attendant is a thread and collectively known as thread pool. Remember the manager by himself does not take any calls, he is just there to delegate calls to the call attendants and manage them
When the first call 'RunSecondsAsync(2.5)' comes in, the manager immediately assigns it to one of the call attendant and lets you know that the call has been addressed with the help of task object as return. You again get an immediate second call 'RunSecondsAsync(2)', which the manager immediately assigns to another call attendant and both the calls are handled simultaneously.
However you want to log the amount of time spent on the phone calls, so you wait for those calls to be completed with the help of await keywords. The key difference of waiting this time is, you are still free to do whatever you want because the phone calls are attended by call attendants.So if you get a call from your home this time around you will be able to take it. (analogous to application being responsive).
Once the calls are done, the manager lets you know that the calls are completed and you go ahead and log in your diary. Now coming to logging of the calls, you first log both the calls which have come in and once they are completed you log in the total time spent on each call. The total duration spent by you in this case is close to 2.5 seconds which is the maximum of both calls because calls are handled in parallel and some overhead in communicating with the manager.
Thread started to run for 2.5 seconds
Thread started to run for 2 seconds
Stopwatch passed 2002 ms.
Stopwatch passed 2554 ms.
Console.WriteLine("\Multithreading:");
Then you log "Multithreading:" into the diary
Calling method 'RunSecondsThreaded()'
RunSecondsThreaded(2.5); RunSecondsThreaded(2);
And finally you and your manager have a small fight and he leaves the organization. However you do not want to take the calls because you have other important tasks to take care of. So you hire a call attendant when a phone call comes in and have the work done for you. You do it two times because two calls have come by. Meanwhile you are again free to do other tasks like if you get a phone call from your home you can attend it.
Now coming to logging of the calls. You do not log the calls this time around into the diary. The call attendants do it on your behalf. The work done by you is just hiring the call attendants. Since calls have come in almost at the same time, the total time spent is 2.5 seconds plus some additional time for hiring.
Thread started to run for 2.5 seconds
Thread started to run for 2 seconds
Stopwatch passed 2000 ms.
Stopwatch passed 2501 ms.
Hope it helps in resolving your confusion

Is there any point starting a LongRunning Task if the parent immediately Waits?

I am working on some legacy code which repeatedly calls a long running task in a new thread:
var jobList = spGetSomeJobIds.ToList();
jobList.ForEach((jobId) =>
{
var myTask = Task.Factory.StartNew(() => CallExpensiveStoredProc(jobId),
TaskCreationOptions.LongRunning);
myTask.Wait();
});
As the calling thread immediately calls Wait and blocks until the task completes I can't see any point in the Task.Factory.StartNew code. Am I missing something? is there something about TaskCreationOptions.LongRunning which might add value?
As msdn says:
Waits for the Task to complete execution.
in addition, there is the following statement:
Wait blocks the calling thread until the task completes.
So myTask.Wait(); looks redundant as method CallExpensiveStoredProc returns nothing.
As a good practise, it would be better to use async and await operators when you deal with asyncronous operations such as database operations.
UPDATE:
What we have is:
We run LongRunning, so new Thread is created. It can be seen in source files.
Then we call myTask.Wait();. This method just waits when myTask will finish its work. So all jobList iterations will be executed sequantially, not parallely. So now we need to decide how our job should be executed - sequantially(case A) or parallelly(case B).
Case A: Sequantial execution of our jobs
If your jobs should be executed sequntially, then a few questions might be arisen:
what for do we use multithreading, if our code is executing sequantially? Our code should be clean and simple. So we can avoid using multithreading in this case
when we create a new thread, we are adding additional overheads to the threadpool. Because thread pool tries to determine the optimal number of threads and it creates at least one thread per core. That means when all of the thread pool threads are busy, the task might wait (in extreme cases infinitely long), until it actually starts executing.
To sum up, so there is no gain in this case to create new Thread, especially new thread using LongRunning enum.
Case B: Parallel execution of our jobs
If our goal is to run all jobs parallely, then myTask.Wait(); should be eliminated because it makes code to be executed sequntially.
Code to test:
var jobs = new List<int>(){1, 2, 3 };
jobs.ForEach(j =>
{
var myTask = Task.Factory.StartNew(() =>
{
Console.WriteLine($"This is a current number of executing task: { j }");
Thread.Sleep(5000); // Imitation of long-running operation
Console.WriteLine($"Executed: { j }");
}, TaskCreationOptions.LongRunning);
myTask.Wait();
});
Console.WriteLine($"All jobs are executed");
To conclude in this case B, there is no gain to create new Thread, especially new thread using LongRunning enum. Because this is an expensive operation in the time it takes to be created and in memory consumption.

Using Task.Yield to overcome ThreadPool starvation while implementing producer/consumer pattern

Answering the question: Task.Yield - real usages?
I proposed to use Task.Yield allowing a pool thread to be reused by other tasks. In such pattern:
CancellationTokenSource cts;
void Start()
{
cts = new CancellationTokenSource();
// run async operation
var task = Task.Run(() => SomeWork(cts.Token), cts.Token);
// wait for completion
// after the completion handle the result/ cancellation/ errors
}
async Task<int> SomeWork(CancellationToken cancellationToken)
{
int result = 0;
bool loopAgain = true;
while (loopAgain)
{
// do something ... means a substantial work or a micro batch here - not processing a single byte
loopAgain = /* check for loop end && */ cancellationToken.IsCancellationRequested;
if (loopAgain) {
// reschedule the task to the threadpool and free this thread for other waiting tasks
await Task.Yield();
}
}
cancellationToken.ThrowIfCancellationRequested();
return result;
}
void Cancel()
{
// request cancelation
cts.Cancel();
}
But one user wrote
I don't think using Task.Yield to overcome ThreadPool starvation while
implementing producer/consumer pattern is a good idea. I suggest you
ask a separate question if you want to go into details as to why.
Anybody knows, why is not a good idea?
There are some good points left in the comments to your question. Being the user you quoted, I'd just like to sum it up: use the right tool for the job.
Using ThreadPool doesn't feel like the right tool for executing multiple continuous CPU-bound tasks, even if you try to organize some cooperative execution by turning them into state machines which yield CPU time to each other with await Task.Yield(). Thread switching is rather expensive; by doing await Task.Yield() on a tight loop you add a significant overhead. Besides, you should never take over the whole ThreadPool, as the .NET framework (and the underlying OS process) may need it for other things. On a related note, TPL even has the TaskCreationOptions.LongRunning option that requests to not run the task on a ThreadPool thread (rather, it creates a normal thread with new Thread() behind the scene).
That said, using a custom TaskScheduler with limited parallelism on some dedicated, out-of-pool threads with thread affinity for individual long-running tasks might be a different thing. At least, await continuations would be posted on the same thread, which should help reducing the switching overhead. This reminds me of a different problem I was trying to solve a while ago with ThreadAffinityTaskScheduler.
Still, depending on a particular scenario, it's usually better to use an existing well-established and tested tool. To name a few: Parallel Class, TPL Dataflow, System.Threading.Channels, Reactive Extensions.
There is also a whole range of existing industrial-strength solutions to deal with Publish-Subscribe pattern (RabbitMQ, PubNub, Redis, Azure Service Bus, Firebase Cloud Messaging (FCM), Amazon Simple Queue Service (SQS) etc).
After a bit of debating on the issue with other users - who are worried about the context switching and its influence on the performance.
I see what they are worried about.
But I meant: do something ... inside the loop to be a substantial task - usually in the form of a message handler which reads a message from the queue and processes it. The message handlers are usually user defined and the message bus executes them using some sort of dispatcher. The user can implement a handler which executes synchronously (nobody knows what the user will do), and without Task.Yield that will block the thread to process those synchronous tasks in a loop.
Not to be empty worded i added tests to github: https://github.com/BBGONE/TestThreadAffinity
They compare the ThreadAffinityTaskScheduler, .NET ThreadScheduler with BlockingCollection and .NET ThreadScheduler with Threading.Channels.
The tests show that for Ultra Short jobs the performance degradation is
around 15%. To use the Task.Yield without the performance degradation (even small) - it is not to use extremely short tasks and if the task is too short then combine shorter tasks into a bigger batch.
[The price of context switch] = [context switch duration] / ([job duration]+[context switch duration]).
In that case the influence of the switching the tasks is negligible on the performance. But it adds a better task cooperation and responsiveness of the system.
For long running tasks it is better to use a custom Scheduler which executes tasks on its own dedicated thread pool - (like the WorkStealingTaskScheduler).
For the mixed jobs - which can contain different parts - short running CPU bound, asynchronous and long running code parts. It is better to split the task into subtasks.
private async Task HandleLongRunMessage(TestMessage message, CancellationToken token = default(CancellationToken))
{
// SHORT SYNCHRONOUS TASK - execute as is on the default thread (from thread pool)
CPU_TASK(message, 50);
// IO BOUND ASYNCH TASK - used as is
await Task.Delay(50);
// BUT WRAP the LONG SYNCHRONOUS TASK inside the Task
// which is scheduled on the custom thread pool
// (to save threadpool threads)
await Task.Factory.StartNew(() => {
CPU_TASK(message, 100000);
}, token, TaskCreationOptions.DenyChildAttach, _workStealingTaskScheduler);
}

async Task - What actually happens on the CPU?

I've been reading about Tasks after asking this question and seeing that I completely misunderstood the concept. Answers such as the top answers here and here explain the idea, but I still don't get it.
So I've made this a very specific question: What actually happens on the CPU when a Task is executed?
This is what I've understood after some reading: A Task will share CPU time with the caller (and let's assume the caller is the "UI") so that if it's CPU-intensive - it will slow down the UI. If the Task is not CPU-intensive - it will be running "in the background". Seems clear enough …… until tested. The following code should allow the user to click on the button, and then alternately show "Shown" and "Button". But in reality: the Form is completely busy (-no user input possible) until the "Shown"s are all shown.
public Form1()
{
InitializeComponent();
Shown += Form1_Shown;
}
private async void Form1_Shown(object sender, EventArgs e)
{
await Doit("Shown");
}
private async Task Doit(string s)
{
WebClient client = new WebClient();
for (int i = 0; i < 10; i++)
{
client.DownloadData(uri);//This is here in order to delay the Text writing without much CPU use.
textBox1.Text += s + "\r\n";
this.Update();//textBox1.
}
}
private async void button1_Click(object sender, EventArgs e)
{
await Doit("Button");
}
Can someone please tell me what is actually happening on the CPU when a Task is executed (e.g. "When the CPU is not used by the UI, the Task uses it, except for when… etc.")?
The key to understanding this is that there are two kinds of tasks - one that executes code (what I call Delegate Tasks), and one that represents a future event (what I call Promise Tasks). Those two tasks are completely different, even though they're both represented by an instance of Task in .NET. I have some pretty pictures on my blog that may help understand how these types of task are different.
Delegate Tasks are the ones created by Task.Run and friends. They execute code on the thread pool (or possibly another TaskScheduler if you're using a TaskFactory). Most of the "task parallel library" documentation deals with Delegate Tasks. These are used to spread CPU-bound algorithms across multiple CPUs, or to push CPU-bound work off a UI thread.
Promise Tasks are the ones created by TaskCompletionSource<T> and friends (including async). These are the ones used for asynchronous programming, and are a natural fit for I/O-bound code.
Note that your example code will cause a compiler warning to the effect that your "asynchronous" method Doit is not actually asynchronous but is instead synchronous. So as it stands right now, it will synchronously call DownloadData, blocking the UI thread until the download completes, and then it will update the text box and finally return an already-completed task.
To make it asynchronous, you have to use await:
private async Task Doit(string s)
{
WebClient client = new WebClient();
for (int i = 0; i < 10; i++)
{
await client.DownloadDataTaskAsync(uri);
textBox1.Text += s + "\r\n";
this.Update();//textBox1.
}
}
Now it's returning an incomplete task when it hits the await, which allows the UI thread to return to its message processing loop. When the download completes, the remainder of this method will be queued to the UI thread as a message, and it will resume executing that method when it gets around to it. When the Doit method completes, then the task it returned earlier will complete.
So, tasks returned by async methods logically represent that method. The task itself is a Promise Task, not a Delegate Task, and does not actually "execute". The method is split into multiple parts (at each await point) and executes in chunks, but the task itself does not execute anywhere.
For further reading, I have a blog post on how async and await actually work (and how they schedule the chunks of the method), and another blog post on why asynchronous I/O tasks do not need to block threads.
As per your linked answers, Tasks and Threads are totally different concepts, and you are also getting confused with async / await
A Task is just a representation of some work to be done. It says nothing about HOW that work should be done.
A Thread is a representation of some work that is running on the CPU, but is sharing the CPU time with other threads that it can know nothing about.
You can run a Task on a Thread using Task.Run(). Your Task will run asynchronously and independently of any other code providing a threadpool thread is available.
You can also run a Task asynchronously on the SAME thread using async / await. Anytime the thread hits an await, it can save the current stack state, then travel back up the stack and carry on with other work until the awaited task has finished. Your Doit() code never awaits anything, so will run synchronously on your GUI thread until complete.
Tasks use the ThreadPool you can read extensively about what it is and how it works here
But in a nutshell, when a task is executed, the Task Scheduler looks in the ThreadPool to see if there is a thread available to run the action of the task. If not, it's going to be queued until one becomes available.
A ThreadPool is just a collection of already-instantiated threads made available so that multithreaded code can safely use concurrent programming without overwhelming the CPU with context-switching all the time.
Now, the problem with your code is that even though you return an object of type Task, you are not running anything concurrently - No separate thread is ever started!
In order to do that, you have two options, either you start yourDoit method as a Task, with
Option1
Task.Run(() => DoIt(s));
This will run the whole DoIt method on another thread from the Thread Pool, but it will lead to more problems, because in this method, you're trying to access UI-controls. therefore, you will need either to marshal those calls to the UI thread, or re-think your code so that the UI access is done directly on the UI thread after the asynchronous tasks completes.
Option 2 (preferred, if you can)
You use .net APIs which are already asynchronous, such as client.DownloadDataTaskAsync(); instead of client.DownloadData();
now, in your case, the problem is that you will need to have 10 calls, which are going to return 10 different objects of type Task<byte[]> and you want to await on the completion of all of them, not just one.
In order to do this, you will need to create a List<Task<byte[]>> returnedTasks and you will add to it all returned value from DownloadDataTaskAsync(). then, once this is done, you can use the following return value for your DoIt method.
return Task.WhenAll(returnedTasks);

Task continuation parallel execution with async/await

In the context of a console application making use of async/await constructs, I would like to know if it's possible for "continuations" to run in parallel on multiple threads on different CPUs.
I think this is the case, as continuations are posted on the default task scheduler (no SynchronizationContext in console app), which is the thread pool.
I know that async/await construct do not construct any additional thread. Still there should be at least one thread constructed per CPU by the thread pool, and therefore if continuations are posted on the thread pool, it could schedule task continuations in parrallel on different CPUs ... that's what I thought, but for some reason I got really confused yesterday regarding this and I am not so sure anymore.
Here is some simple code :
public class AsyncTest
{
int i;
public async Task DoOpAsync()
{
await SomeOperationAsync();
// Does the following code continuation can run
// in parrallel ?
i++;
// some other continuation code ....
}
public void Start()
{
for (int i=0; i<1000; i++)
{ var _ = DoOpAsync(); } // dummy variable to bypass warning
}
}
SomeOperationAsync does not create any thread in itself, and let's say for the sake of the example that it just sends some request asynchronously relying on I/O completion port so not blocking any thread at all.
Now, if I call Start method which will issue 1000 async operations, is it possible for the continuation code of the async method (after the await) to be run in parallel on different CPU threads ? i.e do I need to take care of thread synchronization in this case and synchronize access to field "i" ?
Yes, you should put thread synchronization logic around i++ because it is possible that multiple threads would be executing code after await at the same time.
As a result of your for loop, number of Tasks will be created. These Tasks will be executed on different Thread Pool threads. Once these Tasks are completed the continuation i.e. the code after the await, will be executed again on different Thread Pool threads. This makes it possible that multiple threads would be doing i++ at the same time
Your understanding is correct: in Console applications, by default continuations will be scheduled to the thread pool due to the default SynchronizationContext.
Each async method does start synchronously, so your for loop will execute the beginning of DoOpAsync on the same thread. Assuming that SomeOperationAsync returns an incomplete Task, the continuations will be scheduled on the thread pool.
So each of the invocations of DoOpAsync may continue in parallel.

Categories