How does Task.CurrentId work?
Asked Answered
D

3

16

I am currently learning how to use Tasks, async and await in Windows Store ("Metro") apps. I stumbled upon the property Task.CurrentId and try to understand how it works.

According to MSDN it returns "An integer that was assigned by the system to the currently-executing task". So I added logging this value to my custom logger, but to my surprise none of my test apps ever logged anything but null.

Look at this example:

private async void TestButton_Click(object sender, RoutedEventArgs e)
{
    int? id1 = Task.CurrentId;

    await Task.Delay(100);
    int? id2 = Task.CurrentId;

    StorageFolder folder = ApplicationData.Current.LocalFolder;
    StorageFile file = await folder.CreateFileAsync("test.txt", 
        CreationCollisionOption.OpenIfExists);
    int? id3 = Task.CurrentId;

    await FileIO.AppendTextAsync(file, "test");
    int? id4 = Task.CurrentId;

    await DoMoreAsync();
    int? id7 = Task.CurrentId;
}

private async Task DoMoreAsync()
{
    StorageFolder folder = ApplicationData.Current.LocalFolder;
    StorageFile file = await folder.CreateFileAsync("test.txt",
        CreationCollisionOption.OpenIfExists);
    int? id5 = Task.CurrentId;

    await FileIO.AppendTextAsync(file, "test");
    int? id6 = Task.CurrentId;
}

All these ids are null. Why? This code does create tasks, doesn't it? Shouldn't they have an id?

Decision answered 18/1, 2013 at 12:42 Comment(0)
D
22

Because you're using async / await, all your Task.CurrentId calls are happening on the UI thread.

The UI thread is not a Task, so it has a null CurrentId


If you create a Task, its CurrentId will be set:

int? idTask = await Task.Run( () => Task.CurrentId );
Desalinate answered 18/1, 2013 at 12:45 Comment(4)
Aha. So the call to the implicitly created callback (for the code after await) is dispatched to the UI thread?Decision
Yes, a custom windows message is posted to the message loop. When it is handled, the "continuation" is executed as a normal method on the UI thread. You can see the call stack if you set a break point after an await that yields.Desalinate
So continuation is dispatched to UI thread, or to thread from which is initiated async task? Or there are way to configure task how manage continuation?Cenozoic
What is the difference between 'Task.CurrentId' and 'Thread.CurrentThread.ManagedId' ?Scabbard
I
17

According to MSDN it returns "An integer that was assigned by the system to the currently-executing task" ... This code does create tasks, doesn't it? Shouldn't they have an id?

The key there is "currently-executing".

There are two types of Tasks: one type of task executes code, and the other type of task is just an abstract representation of some kind of operation or event. For examples, Task.Run will create a code-executing task that will execute its code on a thread pool thread; Task.Delay will create an operation/event task that completes when a timer fires.

The tasks returned by async methods represent the method itself, so they are actually an operation/event task instead of a code task. The code in async methods may run as delegates or as several different tasks (or a mixture of both). Even if you do get a CurrentId from within an async method (which indicates you're running inside a code-executing task), that id would be different than the id of the task returned by the method (which is always an operation/event task).

Ischia answered 18/1, 2013 at 14:42 Comment(2)
is there an overview of these task/async internals somewhere? Thanks!Latashialatch
@MikeJansen: I describe the two types on my blog, with another post specifically dealing with CurrentId.Ischia
I
0

Here is another example, that gives more insights about what's going on:

Task task1 = Task.Delay(500);
Task task2 = Task.Run(async () =>
{
    Thread.Sleep(100);
    Console.WriteLine($"Task.CurrentId:" + Task.CurrentId);
    await task1;
    Console.WriteLine($"Task.CurrentId:" + Task.CurrentId);
});
Console.WriteLine($"task1.Id:" + task1.Id);
Console.WriteLine($"task2.Id:" + task2.Id);
await task2;

Output:

task1.Id:1
task2.Id:2
Task.CurrentId:3
Task.CurrentId:

Online demo.

The Task.Run with async delegate is a combination of the Task.Factory.StartNew and the Unwrap. The Task.Factory.StartNew returns a nested Task<Task>, and the Unwrap combines these two tasks, the outer and the inner, to one Task. The Task.CurrentId has a value while the outer task is running, and becomes null when the outer task has completed and the inner task is launched. The inner task is a promise-style task, not a delegate-based task like the outer task. So typically it is not running on a specific thread during its execution. It might use multiple threads, and it might not use any thread at all for most of its lifetime.

In the above example the task2 is the unwrapped Task, and has the Id 2. The nested Task<Task> (the outer task) has the Id 3, as we can see by the Task.CurrentId before the await. After the await the outer task has completed, and the inner task has been launched. The inner task is async-generated.

The static CurrentId property is implemented internally like this:

[ThreadStatic]
internal static Task? t_currentTask;  // The currently executing task.

public static int? CurrentId
{
    get
    {
        Task? currentTask = t_currentTask;
        if (currentTask != null)
            return currentTask.Id;
        else
            return null;
    }
}

The t_currentTask is a ThreadStatic field that gets a value when the execution of a task starts on a thread, and reverts to its previous value when the execution completes. So as you can see this feature is heavily associated with threads. When the promise-style tasks came into play at a later stage of the .NET evolution, the CurrentId was left behind. It was probably intended as a debugging feature anyway, for facilitating testing and for Debug assertions. It was not intended for controlling the execution flow of production apps. I can see it used in the whole dotnet/runtime repository in only 11 files, always for logging, testing, and debugging.

Inessential answered 23/8, 2023 at 13:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.