-1

I want to test an async method through of multiple threads, so I create and then call method like below:

public async Task Foo()
{ 
    // some aync code here
}

and create thread:

var tasks = new List<Task>();
      
for (int i = 0; i < 3; i++)
{
    tasks.Add(Task.Run(() => Foo(i.ToString())));
}

await Task.WhenAll(tasks.ToArray());

First of all I want to know that is correct way that I test this method like this or should I do something different?

And second I see in some articles that say if I call an async like below:

 var t1 = Foo("1");
 var t2 = Foo("2");

 await Task.WhenAll(t1,t2);

Then these code will run in separate Thread, and for above code as soon as I put Foo("1") on var t1 it runs immediately in separate Thread and for t2 too, so if it is true so we can say the above codes are same? (I mean executed in same way)

What is the difference between the above codes? (These codes are on Asp.net core API)

pmn
  • 2,176
  • 6
  • 29
  • 56
  • 2
    task and threads are *not* the same https://stackoverflow.com/questions/4130194/what-is-the-difference-between-task-and-thread – Soleil Jan 03 '21 at 15:46
  • @Soleil-MathieuPrévot yes i know that they are different, but finally Task Use `ThreadPool thread`, and my question is about which one of above ways for doing Task using seperate Thread – pmn Jan 03 '21 at 17:25
  • It's not because ThreadPool is used that it is guaranteed that a new thread will be started; the task might be as well a function "queued" into an existing thread. The only way to "queue" into a know thread is the UI thread with `Application.Current.Dispatcher.Invoke(()=>{});`. You want to read this https://devblogs.microsoft.com/dotnet/configureawait-faq/ – Soleil Jan 03 '21 at 19:21
  • @Soleil-MathieuPrévot you mean that Task.Run didnt getting a Thread form ThreadPool? (these codes are on Asp.net core API) – pmn Jan 03 '21 at 19:33
  • @pejman: I recommend reading my [async intro](https://blog.stephencleary.com/2012/02/async-and-await.html) to see how `async`/`await` has nothing to do with threads, and follow up with [There Is No Thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html) to drive the point home. – Stephen Cleary Jan 05 '21 at 14:09
  • @StephenCleary I completely familiar with you and your intro, i study multi times but i need to ask some question to resolve my confusing, i really dont know how can i realize what exactlz occured behind the scene, i read ur new link now i hope this help my confusing :(, thanks – pmn Jan 05 '21 at 19:22
  • @StephenCleary i really like to say , what is think about what happend on a simple senario(like sendAsync http call) and if is it possible u said ur idea about my senario,if your are agree i can open a new issue in stackoverflow, thnaks alot – pmn Jan 05 '21 at 19:33
  • You've got a good answer already here, plus some links for additional information. I don't see the need for another question unless it's a different question. – Stephen Cleary Jan 05 '21 at 20:28
  • @StephenCleary so i can edit above question too and adding more explantion – pmn Jan 05 '21 at 20:33

2 Answers2

0

A Task called directly will only run concurrently if it: both hits an await, and ConfigureAwait(false) is used, it will run on a separate thread after that point. Or if called via Task.Run() or Task.StartNew()`.

Otherwise it will run on the caller's thread.

Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • so when i use this code = ` var t1 = Foo("1"); var t2 = Foo("2");` the `Foo` function does will run after putting in t1 and t2 but in not a seperate Thread? – pmn Jan 03 '21 at 16:05
  • All code in the first call up to the first `await` runs synchronously – Charlieface Jan 03 '21 at 16:08
  • so when code arrive to ` var t1 = Foo("1")` it return an `uncompleted Task` and for `var t2 = Foo("2");` too, then in `await` it will run asyn and seperate thread for each Task on `await` (i mean t1 and t2)? – pmn Jan 03 '21 at 16:13
  • Not without `ConfigureAwait(false)`, although caveat that that only applies in a GUI application – Charlieface Jan 03 '21 at 16:14
  • we can say that second way if use `ConfigureAwait(false)` is the same with first way? – pmn Jan 03 '21 at 16:49
  • Correct when used with `ConfigureAwait`, but only after the first await statement reached – Charlieface Jan 03 '21 at 17:06
0

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.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116