Reputation: 197
EDIT: Updating LongTask()
to be await Task.Delay(2000);
makes it work how I need, so thanks for the answers, y'all!
I am trying to call an async function from within a sync function to let the sync function keep going before the async function is finished. I don't know why the code below waits for LongTask()
to complete before continuing. I thought it should only do that if I did await LongTask()
. Can someone help me understand what I'm missing?
C# fiddle here
using System;
using System.Diagnostics;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
var timer = new Stopwatch();
timer.Start();
LongTask();
timer.Stop();
long ms = timer.ElapsedMilliseconds;
Console.WriteLine(ms.ToString());
}
async static void LongTask()
{
Task.Delay(2000).Wait();
}
}
Upvotes: 3
Views: 3111
Reputation: 12610
This will hopefully demonstrate it better:
static async Task Main(string[] args)
{
var timer = Stopwatch.StartNew();
var t1 = LongTask();
var t2 = LongTask();
Console.WriteLine("Started both tasks in {0}", timer.Elapsed);
await Task.WhenAll(t1, t2);
timer.Stop();
Console.WriteLine("Tasks finished in {0}", timer.Elapsed);
}
async static Task LongTask()
{
await Task.Delay(2000);
}
Notice the change in return types of functions. Notice the output, too. Both task are waiting for something (in this case delay to expire) in parallel and at the same time, the Main
function is able to continue running, printing text and then waiting for the outcome of those functions.
It is important to understand that the execution of an async function runs synchronously until it hits the await
operator. If you don't have any await
in an async function it will not run asynchronously (and compiler should warn about it). This is applied recursively (if you have several nested awaits) until you hit something that is actually going to be waited for. See example:
static async Task Main(string[] args)
{
var timer = Stopwatch.StartNew();
var t1 = LongTask(1);
Console.WriteLine("t1 started in {0}", timer.Elapsed);
var t2 = LongTask(2);
Console.WriteLine("Started both tasks in {0}", timer.Elapsed);
await Task.WhenAll(t1, t2);
timer.Stop();
Console.WriteLine("Tasks finished in {0}", timer.Elapsed);
}
async static Task LongTask(int id)
{
Console.WriteLine("[{0}] Before subtask 1", id);
await LongSubTask1();
Console.WriteLine("[{0}] Before subtask 2", id);
await LongSubTask2();
Console.WriteLine("[{0}] After subtask 1", id);
}
async static Task LongSubTask1(int id)
{
Console.WriteLine("[{0}] LongSubTask1", id);
await Task.Delay(1000);
}
async static Task LongSubTask2(int id)
{
Console.WriteLine("[{0}] LongSubTask2", id);
await Task.Delay(1000);
}
If you run it, you will see that the execution runs synchronously all the way down to Task.Delay
call and only then it returns all the way back to the Main
function.
In order to benefit from async/await, you have to use it all the way down to where it ends up calling some I/O (network, disk, etc.) you will need to wait for (or some other event that does not require constant polling to figure out, or artificially created like delay in that sample). If you need to do some expensive computation (that does not have anything async in it) without occupying some particular thread (say, UI thread in GUI application), you'd need to explicitly start new task with it, which at some later point you can await
for to get its result.
Upvotes: 1
Reputation: 74605
Here's the short short intro to async/await
You know when you're playing some God type game where you e.g. set your settlers building a load of buildings, or cutting trees/making food etc and then you have literally nothing else to do except sit and watch them finish, then you can start again, getting the buildings to churn out knights or catapults or whatever?
It's a bit of a waste of life, you sitting there doing nothing other than waiting for something to finish, then you can carry on with some other task..
As a human you probably don't do that; you go make a cup of coffee, call a friend, hit the punchbag, maybe even go play the space exploration game, set the autopilot of whatever ship to guide you to some star system on a journey that'll take 5 mintues of waiting and watching stars pass.. Autopilot set, coffee made, buildings still not finished, bored of the punchbag.. So you go clean up..
This isn't doing things in parallel; you aren't wiping the floor with a cloth while stirring the coffe and chatting on the phone, but it's making great use of your time, cracking through the todo list by pouring a load of energy into one thing, getting as far as you can, then switching to the next thing. You do one thing at once, but when you get stuck you move onto another thing. If you sat and waited for everything you'd need 10 copies of you to get 10 stop/start jobs done
async/await is the same thing. It takes syncronous code - something that is done from start to finish in one long operation that is the sole focus of your thread's attention, even if there's a 5 minute Wait()
in the middle of it where it literally sits and does nothing for 5 minutes - and chops it up into a bunch of separate methods that are controlled by a process that allows the thread to pick up where it left off of
Marking a method async
is the indicator to C# that the compiler is allowed to invisbly cut the method up into pieces that can be put down and picked up when needed
await
is the indicator where you're saying "I've reached a point where I need some operation to finish before I can progress any further with this; go and find something else to do". When it encounters an await
your thread will stop work on your code because it's being told it to do nothing more until the background operation completes (it's not important what is doing the work, lets imagine it's some background thread) and your thread will go and do something else until the result is ready. This really simplifies a lot of programming related stuff because you only ever deal with one thread - it's code that behaves like syncronous code but goes and does something else instead of doing nothing when it gets stuck
As a keyword await
has to be followed by something that is awaitable, usually a Task
or Task<T>
- it represents the operation that is already set to happen. When the operation is finished and the thread returns await
also helpfully unwraps the Task object that was controlling this process, and gives you the result..
If you take nothing else away from this, at the very least learn:
async
if you want to use await
inside it. You should generally make the return type of an sync
method as either Task (if it returns no value) or Task<X>
(if it returns a value of type X). You should also call the method a name ending in ..Async
Task<X>
object in return, use await
on it to get the X
you actually wantI said in the comments not to use a console app. Let's imagine you have a Windows Forms app. Win Forms apps typically have one thread that does everything. One thread draws the UI, and when you click a button it comes and runs all the code you put in the button_Click()
handler. While it's doing that it's not doing its normal job of drawing the UI. You can set statusLabel.Text = "Downloading data.."
as the very first line of the method, and you can launch a download of a 1 gig file, and then you can set the status Label to "Finished " + downloadedFile.Length
. You click the button, and you see nothing - the UI of your app jams frozen for 30 seconds while the file downloads. Then it suddenly says "Finished 1024000000" in the label:
void button_Click(object sender, EventArgs e){
statusLabel.Text = "Starting download";
string downloadedFile = new SomeHttpDownloadStringThing("http://..");
statusLabel.Text = "Finished " + downloadedFile.Length;
}
Why? Well.. the thread that draws the UI and would have painted the label pixels on the screen got really busy downloading the file. It went into that SomeHttpDownloadStringThing
method and didn't come out for 30 seconds, then it came out, set the status label text again, then it left your code and went back to whatever land it normally lives in. It drew the pixels on screen "Finished..". It never even knew it had to draw "Starting download".. That data was overwritten well before it picked up its paintbrush again
Let's turn it async:
async void button_Click(object sender, EventArgs e){
statusLabel.Text = "Starting download";
string downloadedFile = await SomeHttpDownloadStringThingAsync("http://..");
statusLabel.Text = "Finished " + downloadedFile.Length;
}
SomeHttpDownloadStringThing
returned a string
. SomeHttpDownloadStringThingAsync
returned a Task<string>
. When the UI thread hits that await
imagine it tells some background thread to go download the file, puts this method on pause, and returns out of it and goes back to drawing the UI. It paints the label as "Starting ..." so you see it, and then maybe finds some other stuff to do. If you had a Timer ticking up counting how long the download had been then your timer would increment nicely too - bcause the UI thread is free to do it, it's not sitting awaiting for Windows to do the download while it does nothing other than wait.
When the download finishes, the thread is called back to where it was and it picks up from there. It doesn't run the first code again, it doesn't set the status label to "Starting" again.
It literally starts from the SomeHttpDownloadStringThingAsync
part as if it had never left, the await
converts the Task<string>
into a string
of the downlaoded file, and then the UI thread can set the "Finished.." text
The whole thing is just like the syncronous version, except that little bit in the middle where the UI thread was allowed to go back to its regular job of drawing pixels on the screen and handling other user interaction
This is why I say console apps are hard work for understanding async/await because their UI doesn't obviously do anything else while an await is in progress, unless you've arranged for something to happen, but that's more work. UI apps automatically have other stuff going on, and if you jam up the threads that do that stuff, they freeze, and turn "Not responding"
If you have an eagle eye, you'll have spotted async void
on the handler even though I said "if you have a void method, make it return Task when you make it async" - winforms event handlers are a bit of a special case in that regard, you cannot make them async Task
but they're an exception rather than a rule. For now, rule of thumb - avoid async void
Upvotes: 6
Reputation: 21
As already mentioned in the comments:
you have to use await Task.Delay(2000)
to tell the compiler that you want run Task.Delay(2000)
asynchronous.
If you take a closer look in the example provided by you the compiler shows a warning:
This async methods lacks 'await'operators and will run synchronously [...]
Upvotes: 2