0

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.")?

Community
  • 1
  • 1
ispiro
  • 26,556
  • 38
  • 136
  • 291
  • 2
    `Doit` runs synchronously. Marking a method as `async Task` does not make it asynchronous in and of itself. In fact, I'd expect you to see a compile warning that you have an async method that doesn't await. – Daniel Kelley Oct 09 '15 at 10:23
  • @DanielKelley OK. So how _do_ I make it run asynchronously? Besides using `DownloadDataAsync`, of course, which defeats the purpose of this test as I want to create my own async Task. – ispiro Oct 09 '15 at 10:25
  • Well, in this case you should await DownloadDataAsync, which is asynchronous. DownloadData is sychronous and so blocks. – Daniel Kelley Oct 09 '15 at 10:27
  • Or you could use `Task.Run` and await it. – wingerse Oct 09 '15 at 10:28
  • @EmpereurAiman That doesn't make sense when there is an async option available. It would unnecessarily run the code on a threadpool thread. – Daniel Kelley Oct 09 '15 at 10:29
  • @DanielKelley See my (updated) comment before your last comment. – ispiro Oct 09 '15 at 10:32
  • @DanielKelley yeah you are right. But `DownloadDataAsync` cannot be awaited. `DownloadDataTaskAsync` is the right method for this job. :) – wingerse Oct 09 '15 at 10:32
  • @EmpereurAiman This is just a test - using a built in method doesn't help me understand the subject. – ispiro Oct 09 '15 at 10:33
  • If you want to make an async method. You will want to use `Task.Run()` which Queues the specified work to run on the thread pool and returns a Task object that represents that work. So you can await it.. – wingerse Oct 09 '15 at 10:36
  • @EmpereurAiman That's the one. The old *Async methods often catch me out. ispiro For IO async methods there is no work for the CPU - see blog.stephencleary.com/2013/11/there-is-no-thread.html – Daniel Kelley Oct 09 '15 at 10:39
  • @EmpereurAiman Are you saying that the whole `await` business is only for methods which already have that somehow embedded in them (and my test is wrong because I've been running the Task non-async)? If so - that's worthy of an answer. – ispiro Oct 09 '15 at 10:40
  • You are also updating the UI from a background thread with `this.Update()` this is bad... – MoonKnight Oct 09 '15 at 10:41
  • @DanielKelley That's exactly the reason I chose an I/O intensive Task for the test. – ispiro Oct 09 '15 at 10:42
  • @Killercam If it were really a background thread - it shouldn't block the UI. (It does.) – ispiro Oct 09 '15 at 10:42
  • You are executing an IO bound operation synchronously. Therefore, as the article explains, your thread is blocking until the operation completes. As your thread is the UI thread your UI hangs. It's not clear where the confusion is in this example. – Daniel Kelley Oct 09 '15 at 10:45
  • What you need is move the forloop into `Task.Run` and return the task. – wingerse Oct 09 '15 at 10:46
  • @DanielKelley Just so I'm sure I understood you correctly - Are you saying that: A Task executed asynchronously _will_ run on a separate thread (thread pool or not) but I failed to run it async because the only way to do that is to either use `Task.Run` or await a method that uses that? If so - that's the answer to my question - I was wrong in wrapping `async` and `Task` into one. You can post that an an answer. (And thank you very much for your time!) – ispiro Oct 09 '15 at 10:53
  • Instead of using `client.DownloadData(uri);//This is here in order to delay the Text writing`, you should edit the code in your answer to use `Thread.Sleep(5000);`, so that people don't try to answer your question with elements you consider off-topic :) – Fabio Salvalai Oct 09 '15 at 12:50
  • @FabioSalvalai That was my first thought. But notice how I'm repeatedly told that a Task does _not_ run on a separate thread - so Thread.Sleep would stop _everything_. Not what I'm looking for. – ispiro Oct 09 '15 at 13:00
  • I believe you might have misunderstood what you've been told. Tasks _when they are executed by a task scheduler_ are run typically on a thread from the thread pool - I.e. another one. (there are exceptions here, but let's not talk about those yet). Having a return value of type Task is not enough to make a method asynchronous. In order to make it run asynchronously, you need to call Run or StartTask with your method passed as an argument. Only then can it be executed on another thread. – Fabio Salvalai Oct 09 '15 at 13:07
  • Also, your download call is a blocking call and effectively does just that : it blocks everything (on the current thread) – Fabio Salvalai Oct 09 '15 at 13:09
  • oh, and yet another comment: I'd hate to break bad news, but you are going to face other problems it's not possible to set the property of a UserControl from another thread, it will throw a _Cross-thread operation not valid_ :) Running the whole `DoIt` as an asynchronous task will lead you to more problems. – Fabio Salvalai Oct 09 '15 at 13:38

3 Answers3

4

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • a) Thanks. b) I have already read some of your blog, so thanks for that too. c) The `DownloadDataTaskAsync` in your code defeats the purpose of it _in my code_. Because what I'm trying to achieve is not to download anything, but rather to run this code: `textBox1.Text += s + "\r\n";` asynchronously. (I'm just using the download to get some time between Text updates.) My question, then, is: How do I make the `textBox1.Text += s + "\r\n";` run asynchronously _without_ the aid of any `...Async` method. What's the "magic" that makes `...Async` code run the way it does, so I can use in my code. – ispiro Oct 09 '15 at 12:31
  • Perhaps I wasn't clear enough. Here's what I'd like to do: I want the `Shown` event handler to start writing to the TextBox every so often without needing to leave the method. At the same time, I'd like to be able to click a button which will similarly write to the TextBox, so that writing will alternate between the two. And I'd like to do this with await only, _not_ with any built-in `…Async` methods magic - I want to learn how to _make_ this happen. – ispiro Oct 09 '15 at 12:57
  • I know the whole await on the `DownloadDataTaskAsync` method doesn't fulfill @ispiro 's need, however, for the sake of completeness, the downside of that code is that the 10 downloads will never be parallelized. Using a `Task.WhenAll` instead (see option 2 in my answer) would allow that. – Fabio Salvalai Oct 09 '15 at 13:44
  • @ispiro: See the `TaskCompletionSource` mentioned in my answer. This is how you implement a "low-level asynchronous task", so to speak. Regarding `every so often`, if you need a delay, use `await Task.Delay(...)`. Or implement your own delay task using a timer with `TaskCompletionSource`. – Stephen Cleary Oct 09 '15 at 15:05
  • 1
    @ispiro: Note that "run `textBox1` code asynchronously" doesn't actually *make sense*. What you can do, however, is run that code *when an asynchronous operation completes*. – Stephen Cleary Oct 09 '15 at 15:14
0

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);
Fabio Salvalai
  • 2,479
  • 17
  • 30
  • Thanks. But as I've written, I've read this stuff and don't understand it. _What happens on the CPU?_ If you're saying that the CPU will run the Task on a thread from the thread pool - then please explain why my code fails. If not - I don't see this as an answer to my question. – ispiro Oct 09 '15 at 10:29
  • I edited my response to go in further details about your code. – Fabio Salvalai Oct 09 '15 at 10:41
  • Thanks. The Download is only there to delay the Text without much CPU use. I've edited the question to make it clearer. – ispiro Oct 09 '15 at 10:47
  • the problem with that example is that there is an asynchronous version of the same API that you can use (my option 2) and that is always a **better** choice over my option 1. however, if you don't have such async API, for instance a Thread.Sleep(), you can always fallback on my option 1. does it answer your question? – Fabio Salvalai Oct 09 '15 at 10:49
  • a) Thanks for your time. b) I'm not trying to download anything. I just used that in order to give the UI CPU time while my Task was waiting for I/O. (But it seems that my mistake was that the Task was _launched_ incorrectly.) – ispiro Oct 09 '15 at 10:56
  • if in the example you replace the download by a Thread.Sleep(), just to show this is an operation that will take some time, I guess it will avoid confusion. however, yes, you summarized it correctly. You need to "launch" a task, which you haven't. In your case, **Option 1** of my answer will do the trick and you will not even need to return a `Task` (you should really return void there) – Fabio Salvalai Oct 09 '15 at 10:59
0

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.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • `You can also run a Task asynchronously on the SAME thread using async / await. Anytime the thread hits an await, ... and carry on with other work until the awaited task has finished.` - So how will the task finish if it's not running? Or are you saying that the **Task** will be on the same thread, but the methods it **awaits** will be on a separate thread? (I don't understand what it means to run asynchronously on one thread.) – ispiro Oct 09 '15 at 10:59
  • "I don't understand what it means to run asynchronously on one thread". That's the crux of your problem. Have a read up on async/await. Each "await" basically chops up the code into chunks. Every time the thread awaits something that hasn't finished, it can go away and run a different chunk. That chunk could be part of a different Task. – GazTheDestroyer Oct 09 '15 at 11:02
  • That sounds exactly like a single-CPU computer's multithreading. But semantics aside, That is exctly what I thought I was doing in my test-code - I was letting the UI run while my code was waiting for the url to respond. So why didn't the UI `go away and run a different chunk` - like accept a user's click on the button? – ispiro Oct 09 '15 at 11:26
  • Because as I say there are no awaits in your DoIt() code, so it never gets a chance to go away. And yes, it is effectively like the old co-operative style multi-threading. – GazTheDestroyer Oct 09 '15 at 11:33
  • But `Doit` is called by `await Doit(...);` - What's the difference between that and `await client.DownloadDataAsync(uri);`? What's the magic that makes `DownloadDataAsync` run async while my code doesn't? – ispiro Oct 09 '15 at 11:37
  • The magic is that DownloadDataAsync() will have an await in it while Doit() doesn't. There is even a compiler warning to prevent what you're doing. Obviously the chain of awaits has to end somewhere. This is usually by awaiting on some Task that gets completed by another thread / IO Completion port / OS callback etc. – GazTheDestroyer Oct 09 '15 at 12:51
  • `Obviously the chain of awaits has to end somewhere` - _That_ is what I'm looking for. Anything above that in the chain is not really asynchronous, it just leads to that. If that is executed on a separate thread - then that's the answer to my question - that there _is_ another thread. And I'd like to know how to harness that. (Perhaps the answer is that it can't be used unless I explicitly use another thread or call an `...Async` method that already does something like that.) – ispiro Oct 09 '15 at 13:05
  • It does not have to be another thread. You could have a Task that completes when a TaskCompletionSource gets set from the UI thread when the user clicks a button. Everything runs on the UI thread, but the tasks run asynchronously. – GazTheDestroyer Oct 09 '15 at 13:08
  • _What's the difference between that and await client.DownloadDataAsync(uri);_ --> without going in too much detail, it will manually start a new thread behind the scenes. You can do _pretty much_ the same with `Task.Run(Action)` if you want to go into much more deatils, know it leverages a legacy API that follows the old IAsyncResult pattern. For reference: the source code is here: http://referencesource.microsoft.com/#System/net/System/Net/webclient.cs,abf2c92daf35d8c2,references – Fabio Salvalai Oct 09 '15 at 13:53