When is it best to use Task.Result instead of waiting for Task

While I have been using async code for some time in .NET, I just recently started to research it and understand what is happening. I just looked through my code and tried to change it, so if the task can be performed in parallel with some work, then this is so. For example:

var user = await _userRepo.GetByUsername(User.Identity.Name); //Some minor work that doesn't rely on the user object user = await _userRepo.UpdateLastAccessed(user, DateTime.Now); return user; 

Now it becomes:

 var userTask = _userRepo.GetByUsername(User.Identity.Name); //Some work that doesn't rely on the user object user = await _userRepo.UpdateLastAccessed(userTask.Result, DateTime.Now); return user; 

I understand that a custom object is now retrieved from the WHILST database when some unrelated work happens. Nevertheless, everything that I saw implies that the result should be rarely used, and it will be preferable to wait, but I donโ€™t understand why I want to wait until my custom object is retrieved if I can execute some other independent logic at the same time?

+5
source share
4 answers

Make sure you donโ€™t bury here:

So, for example: [some correct code] becomes [incorrect code]

NEVER NEVER DO IT.

Your instinct is that you can restructure the flow of control to improve performance, superior and correct. Using Result to do this is WRONG WRONG.

The correct way to rewrite code

 var userTask = _userRepo.GetByUsername(User.Identity.Name); //Some work that doesn't rely on the user object user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now); return user; 

Remember that waiting does not make an asynchronous call . Waiting simply means "if the result of this task is not yet available, go do something else and come back here when it is available." The call is already asynchronous: returns the task .

People seem to think that await has the semantics of shared calling; Is not. Rather, waiting is an allocation operation on the comonad task; it is a task operator, not a call. Usually you see this on method calls simply because it is a common template for abstracting an asynchronous operation as a method. The returned task is an expected thing, not a challenge .

However, all that I saw implies that the result should be used rarely and should be preferred, but I donโ€™t understand why I want to wait until my custom object is retrieved if I can execute some other independent logic at the same time?

Why do you think that using Result will allow you to simultaneously execute other independent logic ??? The result does not allow you to do just that . The result is a synchronous wait. Your thread cannot do any other work while it synchronously waits for the task to complete. Use asynchronous wait to increase efficiency. Remember that await simply means that this workflow cannot go any further until this task is completed, so if it is not completed, find more work and return it later. "To too early await may make you ineffective workflow, because sometimes the workflow can progress, even if the task is not completed.

By all means, navigate where you expect improvement in the efficiency of your workflow , but never change it to Result . You have a deep misunderstanding of how asynchronous workflows work if you think that using Result will ever increase the efficiency of parallelism in your workflow. Examine your beliefs and see if you can understand which one gives you this wrong intuition.

The reason why you should never use Result , as this happens not only because it is inefficient to wait synchronously when you execute an asynchronous workflow. He will ultimately hang your process . Consider the following workflow:

  • task1 represents the task that is planned to be executed in this thread in the future and get the result.
  • the asynchronous function Foo expects task1.
  • task1 is not complete yet, so Foo returns, letting this thread work more. Foo returns a task representing its workflow and signs the completion of this task as completion of task1 .
  • Now the thread can now work in the future, including task1 .
  • task1 ends the execution of the Foo workflow completion trigger and ultimately completes the task representing the Foo workflow.

Now suppose Foo selects Result of task1 . What's happening? Foo synchronously waits for the completion of task1 , waiting for the current thread to appear, which is not happening, because we are in synchronous waiting. The result of the call causes the thread to hang with itself if the task is somehow related to the current thread . Now you can create deadlocks without locks and only one thread! Do not do that.

+9
source

In your case, you can use:

 user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now); 

or perhaps more clearly:

 var user = await _userRepo.GetByUsername(User.Identity.Name); //Some work that doesn't rely on the user object user = await _userRepo.UpdateLastAccessed(user, DateTime.Now); 

The only time you need to touch .Result is when you know that the task is complete. This may be useful in some scenarios where you are trying to avoid creating an async state machine, and you think that there is a good chance that the task completed synchronously (possibly using a local function for the async case), or if you are instead of using, rather than async / await and you are in the callback.

As an example of avoiding a state machine:

 ValueTask<int> FetchAndProcess(SomeArgs args) { async ValueTask<int> Awaited(ValueTask<int> task) => SomeOtherProcessing(await task); var task = GetAsyncData(args); if (!task.IsCompletedSuccessfully) return Awaited(task); return new ValueTask<int>(SomeOtherProcessing(task.Result)); } 

The fact is that if GetAsyncData returns a result with synchronous completion, we completely avoid all async mechanisms.

+4
source

Waiting for Async does not mean that some threads will run your code.

However, this will reduce the time during which your thread will wait idly to complete processes, thereby ending earlier.

Whenever a stream usually needs to wait for something to end, for example, waiting for a web page to load, the database request to complete, writing to disk ends, the asynchronous wait stream will not wait idly while data is being written / retrieved, but looked around if they can do other things instead, and return later after completing the expected task.

This is described with the cook analogy in this inverview with Eric Lippert . Search somewhere in the middle for asynchronous wait.

Eric Lippert compares asynchronous waiting with one (!) Cook who is supposed to make breakfast. After he starts toasting the bread, he can calmly wait until the bread is browned before putting the kettle on the tea, wait until the water boils, before putting the tea leaves in the kettle, etc.

An asynchronous cook, not waiting for the toasted bread, but put on the kettle, and while the water is heating, he puts the tea leaves in the kettle.

Whenever a cook has to wait for something, he looks around and sees if he can do something instead.

A thread in an async function will do something similar. Since a function is asynchronous, you know that there is an expectation in the function. In fact, if you forget to program the wait, your compiler will warn you.

When your thread encounters a wait, it goes up the call stack to find out if it can do something else until it sees the wait, get back on the call stack, etc. As soon as everyone is waiting, he gets off the call to the stack and starts waiting until the first expected process ends.

Upon completion of the expected process, the thread will continue to process applications after waiting until it sees the wait.

It is possible that another thread will continue processing statements that appear after waiting (you can see this in the debugger by checking the thread ID). However, this other stream has the context of the original stream, so it can act as if it were the original stream. No need for mutexes, semaphores, IsInvokeRequired (in winforms), etc. It seems to you that there is one thread.

Sometimes your cook must do something that takes some time without waiting for an expectation, such as slicing tomatoes. In this case, it would be wise to hire another cook and order him to make a cut. At the same time, your cook can continue the eggs that have just finished boiling and the necessary peeling.

In computer terms, this would be if you had big calculations, without waiting for other processes. Pay attention to the difference, for example, writing data to disk. After your stream has ordered that data should be written to disk, it will usually wait until data is written. This is not the case when you do big calculations.

You can hire an extra cook using Task.Run

 async Task<TimeSpan> CalculateSunSet() { // start fetching sunset data. however don't wait for the result yet // you've got better things to do: Task<SunsetData> taskFetchData = FetchSunsetData(); // because you are not awaiting your thread will do the following: Location location = FetchLocation(); // now you need the sunset data, start awaiting for the Task: SunsetData sunsetData = await taskFetchData; // some big calculations are needed, that take 33 seconds, // you want to keep your caller responsive, so start a Task // this Task will be run by a different thread: ask<DateTime> taskBigCalculations = Taks.Run( () => BigCalculations(sunsetData, location); // again no await: you are still free to do other things ... // before returning you need the result of the big calculations. // wait until big calculations are finished, keep caller responsive: DateTime result = await taskBigCalculations; return result; } 
+3
source

Did you read this version?

 var userTask = _userRepo.GetByUsername(User.Identity.Name); //Some work that doesn't rely on the user object user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now); return user; 

This will do the โ€œworkโ€ until the user is checked out, but it also has all the await benefits that are described in Waiting for a completed task, similar to the task. Result?


As suggested, you can also use a more explicit version to check the result of the call in the debugger.

 var userTask = _userRepo.GetByUsername(User.Identity.Name); //Some work that doesn't rely on the user object user = await userTask; user = await _userRepo.UpdateLastAccessed(user, DateTime.Now); return user; 
0
source

Source: https://habr.com/ru/post/1273353/


All Articles