83

I am trying to figure out what the async & await keywords are all about, however the output isn't what I'm expecting.

The console application is as follows:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Foo called");
        var result = Foo(5);

        while (result.Status != TaskStatus.RanToCompletion)
        {
            Console.WriteLine("Thread ID: {0}, Status: {1}", Thread.CurrentThread.ManagedThreadId, result.Status);
            Task.Delay(100).Wait();
        }

        Console.WriteLine("Result: {0}", result.Result);
        Console.WriteLine("Finished.");
        Console.ReadKey(true);
    }

    private static async Task<string> Foo(int seconds)
    {
        return await Task.Run(() =>
            {
                for (int i = 0; i < seconds; i++)
                {
                    Console.WriteLine("Thread ID: {0}, second {1}.", Thread.CurrentThread.ManagedThreadId, i);
                    Task.Delay(TimeSpan.FromSeconds(1)).Wait();
                }

                return "Foo Completed.";
            });
    }
}

The output is:

Foo called
Thread ID: 10, Status: WaitingForActivation
Thread ID: 6, second 0.
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 6, second 1.
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 6, second 2.
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 6, second 3.
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 6, second 4.
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Thread ID: 10, Status: WaitingForActivation
Result: Foo Completed.
Finished..

I was expecting to see the status changing from WaitingForActivation once the method is started.

How can it stay in this state and be active?

BanksySan
  • 27,362
  • 33
  • 117
  • 216
  • And what do you expect status to be when `.FromSeconds(1)).Wait(); ` is called? – Alexei Levenkov Dec 29 '13 at 23:07
  • Hi @AlexeiLevenkov I assumed it would have changed to `Running` or maybe `WaitingForChildrenToComplete` as it's no longer waiting. – BanksySan Dec 29 '13 at 23:09
  • 2
    Neither of these would make sense in your case - see [TaskStatus Enumeration](http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskstatus(v=vs.110).aspx) - task is not running and also not finished its own code... I believe the only one that makes sense is the one you see. – Alexei Levenkov Dec 29 '13 at 23:12
  • @AlexeiLevenkov, I don't get what you mean. The `Foo()` method is running and is outputting the lines with `Thread ID: 6`. If it's not running what's it doing? – BanksySan Dec 29 '13 at 23:16
  • 1
    @AlexeiLevenkov, Aas the problem seems interesting, I've checked that by replacing `Task.Delay` in `Foo` with long running loop instead (calculating complex math for milions of iterations), and status is still only `WaitingForActivation` – Konrad Kokosa Dec 29 '13 at 23:24
  • @KonradKokosa Cheers, as I said below, I tried it with a `Thread.Sleep()` as well and no change. – BanksySan Dec 29 '13 at 23:27
  • I think Lukazoid's answer explains the behavior, my comments above are somewhat wrong as they actually based on incorrect assumption what Task actually represent... – Alexei Levenkov Dec 30 '13 at 06:21
  • 4
    This is a very confusing topic and this question and answer have not cleared anything up for us future internet searchers. Why can no one (NO ONE) explain async and await clearly? No one has ever been able to explain this to me, at all. – Naikrovek Oct 27 '15 at 19:08

5 Answers5

95

For my answer, it is worth remembering that the TPL (Task-Parallel-Library), Task class and TaskStatus enumeration were introduced prior to the async-await keywords and the async-await keywords were not the original motivation of the TPL.

In the context of methods marked as async, the resulting Task is not a Task representing the execution of the method, but a Task for the continuation of the method.

This is only able to make use of a few possible states:

  • Canceled
  • Faulted
  • RanToCompletion
  • WaitingForActivation

I understand that Runningcould appear to have been a better default than WaitingForActivation, however this could be misleading, as the majority of the time, an async method being executed is not actually running (i.e. it may be await-ing something else). The other option may have been to add a new value to TaskStatus, however this could have been a breaking change for existing applications and libraries.

All of this is very different to when making use of Task.Run which is a part of the original TPL, this is able to make use of all the possible values of the TaskStatus enumeration.

If you wish to keep track of the status of an async method, take a look at the IProgress(T) interface, this will allow you to report the ongoing progress. This blog post, Async in 4.5: Enabling Progress and Cancellation in Async APIs will provide further information on the use of the IProgress(T) interface.

Jason
  • 3,844
  • 1
  • 21
  • 40
Lukazoid
  • 19,016
  • 3
  • 62
  • 85
  • `IProgress` only seems to be a 4.5 interface, is there a way to emulate it in 4.0 world? – BanksySan Dec 30 '13 at 00:39
  • @BanksySan: Install the `Microsoft.Bcl.Async` package. – Stephen Cleary Dec 30 '13 at 01:44
  • 1
    If it is about old TPL why am i getting the same result when using `TaskFactory.StartNew` ? http://i.imgur.com/nCjtb3s.png http://msdn.microsoft.com/en-us/library/dd321439(v=vs.110).aspx – Selman Genç Dec 30 '13 at 06:39
  • Your first assumption is right: an async method being executed is not actually running (i.e. it may be await-ing something else). actually in my first answer i wanted to say something like this. – Selman Genç Dec 30 '13 at 06:45
  • @Selman22 Because you are writing the status of the `Task` returned by `Foo`, and not the task returned by `TaskFactory.StartNew`. – Lukazoid Dec 30 '13 at 12:04
  • Could you explain "In the context of methods marked as async, the resulting Task is not a Task representing the execution of the method, but a Task for the continuation of the method" and / or provide a reference? – nh43de Apr 18 '16 at 15:46
  • @nh43de The task returned from `Task.Run()` inside Foo() is actually running. But the async method Foo() returns a task that representing a continuation. Imagine Foo() having multiple `await Task.Run()` calls inside and just `return "Foo Completed."` at the end of the method. How would Foo() represent multiple tasks? In the example it is exra confusing because `return await Task.Run()` looks like it just returns the Task returned from Run(), but that is not it. – Rev Oct 24 '22 at 15:02
29

The reason is your result assigned to the returning Task which represents continuation of your method, and you have a different Task in your method which is running, if you directly assign Task like this you will get your expected results:

var task = Task.Run(() =>
        {
            for (int i = 10; i < 432543543; i++)
            {
                // just for a long job
                double d3 = Math.Sqrt((Math.Pow(i, 5) - Math.Pow(i, 2)) / Math.Sin(i * 8));
            }
           return "Foo Completed.";

        });

        while (task.Status != TaskStatus.RanToCompletion)
        {
            Console.WriteLine("Thread ID: {0}, Status: {1}", Thread.CurrentThread.ManagedThreadId,task.Status);

        }

        Console.WriteLine("Result: {0}", task.Result);
        Console.WriteLine("Finished.");
        Console.ReadKey(true);

The output:

enter image description here

Consider this for better explanation: You have a Foo method,let's say it Task A, and you have a Task in it,let's say it Task B, Now the running task, is Task B, your Task A awaiting for Task B result.And you assing your result variable to your returning Task which is Task A, because Task B doesn't return a Task, it returns a string. Consider this:

If you define your result like this:

Task result = Foo(5);

You won't get any error.But if you define it like this:

string result = Foo(5);

You will get:

Cannot implicitly convert type 'System.Threading.Tasks.Task' to 'string'

But if you add an await keyword:

string result = await Foo(5);

Again you won't get any error.Because it will wait the result (string) and assign it to your result variable.So for the last thing consider this, if you add two task into your Foo Method:

private static async Task<string> Foo(int seconds)
{
    await Task.Run(() =>
        {
            for (int i = 0; i < seconds; i++)
            {
                Console.WriteLine("Thread ID: {0}, second {1}.", Thread.CurrentThread.ManagedThreadId, i);
                Task.Delay(TimeSpan.FromSeconds(1)).Wait();
            }

            // in here don't return anything
        });

   return await Task.Run(() =>
        {
            for (int i = 0; i < seconds; i++)
            {
                Console.WriteLine("Thread ID: {0}, second {1}.", Thread.CurrentThread.ManagedThreadId, i);
                Task.Delay(TimeSpan.FromSeconds(1)).Wait();
            }

            return "Foo Completed.";
        });
}

And if you run the application, you will get the same results.(WaitingForActivation) Because now, your Task A is waiting those two tasks.

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • As the problem seems interesting, I've checked that. There is no `Task.Delay` in `Foo`, I added long running loop instead, and status is still only `WaitingForActivation` – Konrad Kokosa Dec 29 '13 at 23:18
  • I thought `Task.Delay()` created a new task, so I waited on that. If I change the `Task.Delay()` to a `Thread.Sleep()` I get the same output. – BanksySan Dec 29 '13 at 23:20
  • yes,it created a new task, and your actual task waiting that new task – Selman Genç Dec 29 '13 at 23:22
  • @Selman22 wouldn't that make the status be `WaitingForChildrenToComplete`? – BanksySan Dec 29 '13 at 23:28
  • @BanksySan But `FooAsync` has not completed yet. – Lukazoid Dec 29 '13 at 23:29
  • but in documentation it says: `The task has finished executing and is implicitly waiting for attached child tasks to complete.` Your task isn't finish,just waiting. – Selman Genç Dec 29 '13 at 23:29
  • @Selman22 Would you be able to give me a code example that does give the status as `Running` or `WaitingForChildrenToComplete`? – BanksySan Dec 29 '13 at 23:32
  • @BanksySan actually, i try it, i added too long task and never call wait, but still it says WaitingForActivation :D Now i'm curious like you :D – Selman Genç Dec 29 '13 at 23:33
  • i think i found the reason,wait, YES ! :D – Selman Genç Dec 29 '13 at 23:39
  • This answer changes the meaning of the question to one purely about the TPL, and nothing to do with the async-await keywords. This is NOT an answer for the original question. – Lukazoid Dec 30 '13 at 00:00
  • Just noticed... Sorry @Selman22, still, thanks for investigating it. – BanksySan Dec 30 '13 at 00:01
  • Actually my answer is correct and not changing the meaning of question,but i couldn't explain it very well :D if you can do this with your foo method and get running results,let me know. – Selman Genç Dec 30 '13 at 00:18
  • @Lukazoid to get the running result i have to set my variable to my task without using await. could you tell me if i use await how can i get Running result? – Selman Genç Dec 30 '13 at 00:32
  • @BanksySan could you check my answer again, i updated my explanation.i hope it is more clear now – Selman Genç Dec 30 '13 at 06:06
4

I had the same problem. The answers got me on the right track. So the problem is that functions marked with async don't return a task of the function itself as expected (but another continuation task of the function).

So its the "await"and "async" keywords that screws thing up. The simplest solution then is simply to remove them. Then it works as expected. As in:

static void Main(string[] args)
{
    Console.WriteLine("Foo called");
    var result = Foo(5);

    while (result.Status != TaskStatus.RanToCompletion)
    {
        Console.WriteLine("Thread ID: {0}, Status: {1}", Thread.CurrentThread.ManagedThreadId, result.Status);
        Task.Delay(100).Wait();
    }

    Console.WriteLine("Result: {0}", result.Result);
    Console.WriteLine("Finished.");
    Console.ReadKey(true);
}

private static Task<string> Foo(int seconds)
{
    return Task.Run(() =>
    {
        for (int i = 0; i < seconds; i++)
        {
            Console.WriteLine("Thread ID: {0}, second {1}.", Thread.CurrentThread.ManagedThreadId, i);
            Task.Delay(TimeSpan.FromSeconds(1)).Wait();
        }

        return "Foo Completed.";
    });
}

Which outputs:

Foo called
Thread ID: 1, Status: WaitingToRun
Thread ID: 3, second 0.
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 3, second 1.
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 3, second 2.
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 3, second 3.
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 3, second 4.
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Thread ID: 1, Status: Running
Result: Foo Completed.
Finished.
petke
  • 1,345
  • 10
  • 25
1

this code seems to have address the issue for me. it comes for a streaming class, ergo some of the nomenclature.

''' <summary> Reference to the awaiting task. </summary>
''' <value> The awaiting task. </value>
Protected ReadOnly Property AwaitingTask As Threading.Tasks.Task

''' <summary> Reference to the Action task; this task status undergoes changes. </summary>
Protected ReadOnly Property ActionTask As Threading.Tasks.Task

''' <summary> Reference to the cancellation source. </summary>
Protected ReadOnly Property TaskCancellationSource As Threading.CancellationTokenSource

''' <summary> Starts the action task. </summary>
''' <param name="taskAction"> The action to stream the entities, which calls
'''                           <see cref="StreamEvents(Of T)(IEnumerable(Of T), IEnumerable(Of Date), Integer, String)"/>. </param>
''' <returns> The awaiting task. </returns>
Private Async Function AsyncAwaitTask(ByVal taskAction As Action) As Task
    Me._ActionTask = Task.Run(taskAction)
    Await Me.ActionTask '  Task.Run(streamEntitiesAction)
    Try
        Me.ActionTask?.Wait()
        Me.OnStreamTaskEnded(If(Me.ActionTask Is Nothing, TaskStatus.RanToCompletion, Me.ActionTask.Status))
    Catch ex As AggregateException
        Me.OnExceptionOccurred(ex)
    Finally
        Me.TaskCancellationSource.Dispose()
    End Try
End Function

''' <summary> Starts Streaming the events. </summary>
''' <exception cref="InvalidOperationException"> Thrown when the requested operation is invalid. </exception>
''' <param name="bucketKey">            The bucket key. </param>
''' <param name="timeout">              The timeout. </param>
''' <param name="streamEntitiesAction"> The action to stream the entities, which calls
'''                                     <see cref="StreamEvents(Of T)(IEnumerable(Of T), IEnumerable(Of Date), Integer, String)"/>. </param>
Public Overridable Sub StartStreamEvents(ByVal bucketKey As String, ByVal timeout As TimeSpan, ByVal streamEntitiesAction As Action)
    If Me.IsTaskActive Then
        Throw New InvalidOperationException($"Stream task is {Me.ActionTask.Status}")
    Else
        Me._TaskCancellationSource = New Threading.CancellationTokenSource
        Me.TaskCancellationSource.Token.Register(AddressOf Me.StreamTaskCanceled)
        Me.TaskCancellationSource.CancelAfter(timeout)
        ' the action class is created withing the Async/Await function
        Me._AwaitingTask = Me.AsyncAwaitTask(streamEntitiesAction)
    End If
End Sub
David
  • 35
  • 3
-2

I overcome this issue with if anybody interested. In myMain method i called my readasync method like

Dispatcher.BeginInvoke(new ThreadStart(() => ReadData()));

Everything is fine for me now.

Erman Uruk
  • 17
  • 3