Waiting task.run vs wait C #

I searched the website and saw a lot of questions regarding task.run vs waiting for async, but there is this specific use case where I do not quite understand the difference. The script is pretty simple, I think.

await Task.Run(() => LongProcess()); 

against

 await LongProcess()); 

where LongProcess is an asynchronous method with several asynchronous calls in it, for example, with a db call waiting for ExecuteReaderAsync ().

Question:

Is there a difference between the two scenarios? Any help or input appreciated, thanks!

+14
c # asynchronous async-await
source share
3 answers

Task.Run can output an operation that will be processed in another thread. This is the only difference.

This can be useful - for example, if LongProcess not asynchronous, this will cause the caller to return faster. But for a truly asynchronous method, it makes no sense to use Task.Run , and this can lead to unnecessary expenses.

Be careful because the behavior of Task.Run will change depending on the resolution of the overload. In your example, the Func<Task> overload will be selected, which (correctly) wait for the LongProcess . However, if a non-task return delegate was used, Task.Run will only wait until the first await (note that this way TaskFactory.StartNew will always behave, so do not use this).

+12
source share

Quite often, people think that asynchronous waiting is performed by multiple threads. In fact, all this is done in one thread.

See the appendix below about this single thread.

What really helped me understand asynchronous waiting is this interview with Eric Lippert about asynchronous waiting . Somewhere in the middle, he compares the asynchronous wait with the cook, who must wait for the water to boil. Instead of doing nothing, he looks around to see if there is anything else, such as chopped onions. If this is finished and the water still does not boil, he checks to see if there is anything else, and so on, until he has nothing to do but wait. In this case, he returns to the first that he was waiting.

If your procedure calls the expected function, we are sure that somewhere in this expected function there is a call to the expected function, otherwise the function will not be expected. In fact, your compiler will warn you if you forget to wait somewhere in your expected function.

If your expected function calls another expected function, then the thread enters this other function, begins to perform actions in this function, and delves into other functions until it encounters an expectation.

Instead of waiting for the results, the thread goes up the call stack to see if there are any other pieces of code that it can process until it sees the wait. Rise again in the call stack, process until you wait, etc. As soon as everyone expects, the thread searches for the lower expectation and continues as soon as it ends.

This has the advantage that if the caller of your expected function does not need the result of your function, but can do other things before the result is needed, these other things can be executed by the thread instead of waiting inside your function.

A call that does not expect an immediate result will look like this:

 private async Task MyFunction() { Task<ReturnType>taskA = SomeFunctionAsync(...) // I don't need the result yet, I can do something else DoSomethingElse(); // now I need the result of SomeFunctionAsync, await for it: ReturnType result = await TaskA; // now you can use object result } 

Note that in this scenario, everything is executed by a single thread. As long as your topic has something to do, it will be busy.

Addition. It is not true that only one thread is involved. Any thread that has nothing to do can continue to process your code after waiting. If you check the thread identifier, you will see that this identifier can be changed after waiting. The continuing stream has the same context as the original stream, so you can act as if it were the original stream. No need to check InvokeRequired , no need to use mutexes or critical sections. For your code, it is as if one thread was involved.

The article link at the end of this answer explains a little more about the context of the stream.

You will see the expected functions mainly where some other process has to do something, while your thread just has to wait until the other thing is complete. For example, sending data over the Internet, saving a file, connecting to a database, etc.

However, sometimes you need to perform some complex calculations, and you want your thread to be free to do something else, for example, respond to user input. In this barrel, you can run the expected action as if you had called an asynchronous function.

 Task<ResultType> LetSomeoneDoHeavyCalculations(...) { DoSomePreparations() // start a different thread that does the heavy calculations: var myTask = Task.Run( () => DoHeavyCalculations(...)) // now you are free to do other things DoSomethingElse(); // once you need the result of the HeavyCalculations await for it var myResult = await myTask; // use myResult ... } 

Now another thread is doing heavy calculations, while your thread can do other things. As soon as he starts to wait, your caller can do something until he starts to wait. Effectively, your theme will respond fairly freely to user input. However, this will only take place if everyone is waiting. While your thread is busy with things, it cannot respond to user input. Therefore, always make sure that if you think your user interface thread should do some busy processing that takes some time, use Task.Run and let the other thread do this

Another article that helped me: Async-Await from brilliant explanator Stephen Cleary

+13
source share

This answer addresses the specific case of waiting for an asynchronous method in the user interface application event handler. In this case, the first approach has a significant advantage over the second. Before explaining why, let's rewrite the two approaches so that they clearly reflect the context of this answer. The following applies only to user interface application event handlers.

 private async void Button1_Click(object sender, EventArgs args) { await Task.Run(async () => await LongProcessAsync()); } 

against

 private async void Button1_Click(object sender, EventArgs args) { await LongProcessAsync(); } 

I added the Async suffix to the method name to follow the instructions . I also made async an anonymous delegate just for readability. The costs of creating a state machine are negligible and are minimized because of the value of clearly conveying that this Task.Run returns the style of the promise of the Task , rather than the synchronous old-fashioned Task intended for background processing with a processor binding. workloads.

The advantage of the first approach is that it ensures that the user interface remains responsive. The second approach does not provide such a guarantee. As long as you use the built-in asynchronous APIs of the .NET platform, the probability of blocking the user interface in the second approach is rather small. After all, these APIs are implemented by experts. By the time you start expecting your own asynchronous methods, all guarantees are disabled. Unless, of course, your name is not Stephen, but your surname is Taub or Cleary. If this is not the case, then I am sure that sooner or later you will write this code:

 public static async Task LongProcessAsync() { TeenyWeenyInitialization(); // Synchronous await SomeBuildInAsyncMethod().ConfigureAwait(false); // Asynchronous CalculateAndSave(); // Synchronous } 

The problem is obviously related to the TeenyWeenyInitialization() method. This method is synchronous and precedes the first await inside the body of the asynchronous method, so it is not expected. It will work synchronously every time you call LongProcessAsync() . Therefore, if you follow the second approach (without Task.Run ), TeenyWeenyInitialization() will be executed in the user interface thread .

How bad can this be? Initialization is tiny after all! Just a quick trip to the database to get the value, read the first line of a small text file, get the value from the registry. It all ended in a couple of milliseconds. At the time you wrote the program. In your pc. Before moving the data folder on the shared drive. Before the amount of data in the database became huge.

But you can get lucky and TeenyWeenyInitialization() will stay fast forever, how about the second synchronous method, CalculateAndSave() ? This comes after await , which is configured not to capture the context, so it runs in the thread pool thread. It should never run in a user interface thread, right? Wrong. It depends on the Task returned by SomeBuildInAsyncMethod() . If Task completed, the thread will not switch, and CalculateAndSave() will be launched in the same thread that the method called. If you follow the second approach, it will be a UI thread . You may never encounter a situation where SomeBuildInAsyncMethod() returned a completed Task in your development environment, but the production environment may differ in ways that are difficult to predict.

Having an application that works poorly is unpleasant. Having an application that does not work well and freezes the user interface is even worse. Do you really want to take a chance? If you do not, always use Task.Run(async inside your event handlers. Especially while waiting for methods, you encoded yourself!

0
source share

All Articles