8

I asked a question yesterday and the answer was good. But now I'm trying to understand the role of await and how task execution works.

I've read about await that: The await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes. The task represents ongoing work (from msdn site).

Task.run: The Run method allows you to create and execute a task in a single method call and is a simpler alternative to the StartNew method (from msdn site).

Now, with the code:

        public async Task YourFunc()
        {
            Exception error = null;
            try
            {
               var task = Task.Run(() =>
                         {
                             Thread.Sleep(3000);
                             throw new ArgumentException("test argument exception");
                         });
                var completed = task.IsCompleted;
                var faulted = task.IsFaulted;
                Console.WriteLine(completed);
                Console.WriteLine(faulted);
            }
            catch (Exception ex)
            {
                error = ex;
            }
            this.MigrationProcessCompleted(error);
        }

I've removed the await operator and I've set a breakpoint on the line Console.WriteLine(completed);. Why even after 2-3 minutes of waiting in this breakpoint, the task is not completed and not faulted? I've set a breakpoint inside the task's code and exception is trown, so the task must be marked as faulted or completed at least...

Community
  • 1
  • 1
Buda Gavril
  • 21,409
  • 40
  • 127
  • 196
  • 1
    What are you examining after the 2-3 minutes - `task.IsFaulted` or the simple `faulted` variable that sampled that value 3 minutes ago? – Damien_The_Unbeliever Oct 28 '16 at 09:19
  • the task.IsFaulted – Buda Gavril Oct 28 '16 at 09:22
  • 1
    Without await, Task is completed much later than YourFunc exits, so try-catch and actually the whole method is completely useless. both completed and faulted will be false, because at the moment you hit them, task is not completed or faulted yet. As for breakpoint - all threads are suspended while you are on breakpoint, so however long you wait - task will still be in progress. – Evk Oct 28 '16 at 09:33
  • @Evk You were right. So instead of waiting on a breakpoint, I've added waited in a while with thread sleep and measured two minutes (while ((DateTime.Now - now).TotalMinutes < 2)) and after this the task was marked as completed. But why the error was not thrown? – Buda Gavril Oct 28 '16 at 09:42
  • 1
    Error is thrown inside Task.Run, so on another thread, not the thread the rest of YourFunc runs on, and so not on the thread your try-catch block executes. If you use await, it will (conveniently) rethrow this exception on YourFunc thread for you, but if you don't - exception will be lost (and actually might tear down your whole application at some point later, because it was not handled nor observed). As I said before - your whole method _might_ be completed waay before exception is thrown, so there is no way (logically) it can be handled there. – Evk Oct 28 '16 at 09:47
  • 1
    It seems you have a lot of misunderstanding with regards to the TPL and async/await. I recommend that you read [Stephen Cleary's excellent explanation on the subject](http://blog.stephencleary.com/2012/02/async-and-await.html) Bear in mind, that in almost all cases you should avoid using `Task.Run` and `Task.Factory.StartNew`. – Aron Oct 28 '16 at 09:51
  • I see... now I've also read that: If you await a task-returning async method that causes an exception, the await operator rethrows the exception. So await not only that waits for the task to complete, but it also re-throws the exception. Nice! Thank you – Buda Gavril Oct 28 '16 at 09:51
  • @Aron Thank you, I will read this ASAP – Buda Gavril Oct 28 '16 at 09:52

1 Answers1

9

Nobody has really answered your question. You have a reasonable expectation to see task.isCompleted and task.isFaulted be true if you wait more than 3 seconds.

Your expectation is not wrong. The problem here is simply how the debugger is working. While you are stopped at that breakpoint waiting for things, all other threads are also stopped, so nothing is progressing and the exception has not been thrown yet.

This is just a quirk of debugging and you have a few options if you want to see the results you expect:

  1. Freeze the thread instead of using a breakpoint. In order to do this put a breakpoint right after the var task=Task.Run() and then open up your "threads" window in the debugger. Find the current thread (with the yellow arrow) right click on it and select "freeze" then hit F8 or click on continue to let the application keep running. After 3-4 seconds, click the "Pause" button on the debugger and double click on the frozen thread again. You can now check on the values of task.IsCompleted and task.IsFaulted and they should be true.
  2. Add a timeout before you check the tasks:

Code:

var task = Task.Run(() =>{
            Thread.Sleep(3000);
            throw new ArgumentException("test argument exception");
        });
        Thread.Sleep(5000);
        var completed = task.IsCompleted;
        var faulted = task.IsFaulted;
        Console.WriteLine("Completed ::" + completed);
        Console.WriteLine("Faulted ::" + faulted);  

Now you can put a breakpoint after the Thread.Sleep(5000) and confirm that the task is faulted/completed.

Keep in mind, that as others have said, use case of an immediately awaited Task.Run() is almost always a mistake. Keep in mind that the whole point of async/await is to free up the current thread so it's not doing useless waiting. If you do a Task.Run followed by an await you haven't achieved anything since you are just freeing up the current thread (putting it back in the thread pool) but the Task.Run() will take up a thread right back from the thread pool. Conceptually all your doing is moving your work form one thread id to another with no real gain (even if the work in your Task.Run is CPU-bound).

In that case, you are better off not doing Task.Run at all and just doing the work synchronously on the current thread.

Daniel Tabuenca
  • 13,147
  • 3
  • 35
  • 38
  • For actual application, there's no other way except awaiting the Task to ensure that result / exception can be received by the caller. As I have mentioned, this is not truly async way due to invocation of a thread pool thread, when ideal Async has no other thread except the calling thread. Suggestion to not `await Task.Run()` is not correct, please suggest the alternative which can be used in real application using async method, I am certain `Thread.Sleep` is only for the purpose of the debugging the application. – Mrinal Kamboj Oct 30 '16 at 13:37
  • Must say the point related to debugger freezing threads is good and correct, which I missed out, because whole focus was to correctly design the `Async` method using the `Await`. – Mrinal Kamboj Oct 30 '16 at 14:00
  • It didn't sound from his question that this is any real application. This is just throwaway code and he's just trying to understand how it all works. His question was about why he wasn't seeing what he expected to see while debugging. So I concentrated on answering just that. – Daniel Tabuenca Oct 30 '16 at 16:27
  • Alright this is a throw away code, though OP can decide better, still the point of not using `await` for `Task.Run` is incorrect, there's no other mechanism to unwrap an exception for Asynchronous method. All the the other mechanism like `Task.Result` or `ContinueWith` would end up blocking the caller – Mrinal Kamboj Oct 30 '16 at 16:50
  • My comments aren't saying you shouldn't `await` to get the results of Task.Run. I'm saying if you are doing a `Task.Run` followed by an immediate `await` then you likely should not be doing `Task.Run` in the first place since you will not gain anything other than taskpool churn. BTW, `Task.Result` would block the caller, but `ContinueWith` will not. In fact `ContinueWith` is almost an exactly equivalent way of writing an await. See http://stackoverflow.com/questions/8767218/is-async-await-keyword-equivalent-to-a-continuewith-lambda – Daniel Tabuenca Oct 30 '16 at 16:56
  • Mostly there's a misunderstanding here, most certainly `await` frees up the caller and `Continuewith` will not. In current use case, they will work in a similar way, since `Continuewith` will be blocking a different thread than the caller, so the caller goes in parallel, thus leading to an impression that it works same way as `await`. For a genuine async call like file read async or database query async, please try `await` vs `continuewith` and the difference would be explicit, since `ContinueWith` will make it a synchronous operation. – Mrinal Kamboj Oct 30 '16 at 17:23
  • Link that you have pasted, refers to behavior of the `await` where it creates a sort of continuations on same line as `ContinueWith`, but it doesn't refer to the explicit freeing up of the caller, which is can only be done by `await`, as it makes the call truly Async – Mrinal Kamboj Oct 30 '16 at 17:26
  • Also in my view, we might be converging now, since yes in this case `await` is same as `ContinueWith`, in fact even `Result`, will yield same result , due to call not being truly `Async` as `Task.Run` is used for making the operation CPU bound, but in truly Async method, we cannot afford to use them interchangeably. – Mrinal Kamboj Oct 30 '16 at 17:35
  • You seem to have a misunderstanding of what `await` actually does. There's no magic to "explicitly freeing up the caller" all it does is setup a continuation and immediately return the task if the task is not complete. You can return your own task at any point and the caller would be "freed". So if you do `return callSomethingAsync().ContinueWith(()=>Console.WriteLine("Done"))` this will absolutely not block the thread. – Daniel Tabuenca Oct 30 '16 at 17:36
  • You have tried this for a IO call: `return callSomethingAsync().ContinueWith(()=>Console.WriteLine("Don‌​e"))` As per your point, I needn't use `await` for a pure Async code and can replace it with `ContinueWith`, behavior will be exactly same. – Mrinal Kamboj Oct 30 '16 at 17:39
  • Yes, if you call that for an IO call, the calling method will return immediately. It will not wait until the IO call is complete or `Console.WriteLine` executes. It will return right away. This is how asynchronous behavior is achieved before the existence of async/await. TPL existed in .net4 and you could write asynchronous code with it even before `async/await` came to being in .net5. `async/await` is just nicer syntax. – Daniel Tabuenca Oct 30 '16 at 17:44
  • Please check the following link: http://www.c-sharpcorner.com/UploadFile/pranayamr/difference-between-await-and-continuewith-keyword-in-C-Sharp/ Clearly elucidates the difference between await and Continuewith, also the compiler warning on using the Continuewith, https://msdn.microsoft.com/en-us/library/hh873131.aspx. Far from the notion that they can be interchangeably used, in my understanding it is incorrect to do so, especially since await is not a mere syntax, it does lead to generation of state machine to take care of execution state / context – Mrinal Kamboj Oct 31 '16 at 02:54
  • Also review the difference in `IL` generated while using `await` and `ContinueWith`, the working and operation of state machine is clearly seen in case of `await`. In fact before `Async-Await`, `Begin-End` method based APM model, based on `IAsyncResult` was used to make / call a method Asynchronously. Nonetheless thanks for nice discussion, it certainly helped me dig deeper into various concepts. – Mrinal Kamboj Oct 31 '16 at 02:58