You are confusing async-await with multithreading.
What helped me a lot to understand async-await, is this interview with Eric Lippert where he compares async await with a cook who has to make breakfast.
If a cook has to make breakfast, he starts boiling water for the tea. Instead of waiting for the water to boil, he inserts the bread to toast. Again he doesn't wait, but starts slicing tomatoes. After that is finished, he looks around to see if he can do something else instead of waiting idly. Only if he has nothing else to do, he waits idly.
So instead of waiting idly the one and only cook looks around to see if he can do something else instead.
Something similar is done when using async await. Whenever your thread has to wait for another process to finish, like data to be written to a file, queries from a database management system, or fetched from the internet, your thread does not wait idly, but continues processing until he sees an await. The thread goes up the call stack to see if the caller is not awaiting, and stars processing until he sees await. Up the call stack again. etc.
It doesn't really have to be the same thread to execute the statements, it can be any thread in the pool of threads that your program has. If you watch the thread Id, you'll see that after the await the thread Id has changed: a different thread continued. This thread however has the same "context", which means that you can regard it as the original thread: no need for mutexes, critical sections, check for InvokeRequired, etc. As long as you don't start a new thread, the thread after the await can act as if it is the original thread.
var taskWriteFile = this.WriteDataToFileAsync("Hello World!");
var taskFetchData = this.FetchFromDataBaseAsync();
...
Your thread starts executing the statements in WriteDataToFile until it sees an await. Every async method is expected to have at least one await. In fact, your compiler will warn you if you forgot to add the await.
When the thread sees the await, it does not wait idly until the data has been written to the disk, it goes up the call stack to execute the next statement. This is not an await, so the thread starts fetching the data from the database, until it sees an await. Then it goes up the call stack to execute ...
Note: this is all done by one thread (one cook).
As long as the code isn't doing very much, but mostly has to wait for other processes, this is enough to keep your UI thread responsive. There is no need to start a new thread.
Only if your thread has to do some lengthy calculations, he would be too busy to react on operator input, like the cook slicing tomatoes. If you want to keep your UI thread responsive, you can hire a new cook to start slicing tomatoes:
ICollection<TomatoSlice> Slice(Tomato tomato) {...}
To start a different threat to slice tomatoes:
Tomato tomato = this.GrabTomato();
var taskSliceTomatos = Task.Run(() => this.Slic(tomato));
After this your UI thread is free to do something else:
this.DoSomeThingElse();
ICollection<TomatoSlice> tomatoSlices = await taskSliceTomatos;
Note: if your thread has noting else to do (like keeping the UI responsive), it is seldom a good idea to start a new thread to do this. If your cook has nothing to do, he would wait idly for the hired cook to finish slicing tomatoes. He could have done it himself, without the overhead of hiring a cook.
Back to your question
Is Task.Run
, followed by Task.WhenAll
the same?
No, your second code is not the same as the first code, although the post condition will probably be the same.
You should only use Task.Run
if you need your thread to remain responsive and you expect that the task will take some considerable amount of time.
Should I test my code like this?
No. Your procedure is meant to transform a precondition into a postcondition. If the procedure is called by one thread, then it should do this transformation. If one thread does this, then several threads will also do this.
The only reason to start new threads to call the same procedure for the second time while the first one is not finished, would be if you want to test whether the procedure is thread safe.
bool someBool = false;
async Task<bool> DoItAsync()
{
this.someBool = !this.someBool;
await Task.Delay(TimeSpan.FromSeconds(1));
return this.someBool;
}
It is obvious that this procedure is not thread save. If the procedure is reentered while it is already running, the return value will not be the expected one.
Not reentrant:
this.someBool = false;
bool b1 = await this.DoItAsync();
bool b2 = await this.DoItAsync();
Assert.IsTrue(b1);
Assert.IsFalse(b2);
However, if the requirement is that the method would be thread save, the following would be enough to test it:
this.someBool = false;
Task<bool> t1 = this.DoItAsync();
Task<bool> t2 = this.DoItAsync();
await Task.WhenAll(new [] {t1, t2});
Assert.IsTrue(t1.Result);
Assert.IsFalse(t2.Result);
This way, you can be certain that t2 is executed as soon as t1 is awaiting. If you would have started a new Task using Task.Run( () => ...)
there would be no guarantee when the second task is started: before the first task? before or after the first task flips someBool? before or after the await of the first task? We don't know.