0

When calling SetResult on a TaskCompletionSource in C#, is it possible to force the continuations to be called on the same thread, so that SetResult doesn't return until all continuations have executed?

Example:

using System;
using System.Threading;
using System.Threading.Tasks;
                    
public class Program
{
    public static async Task Main()
    {
        var tcs = new TaskCompletionSource<string>(TaskContinuationOptions.ExecuteSynchronously);
        var task = Task.Run(async () =>
        {
            Console.WriteLine("Awaiting task on thread ID: {0}", Thread.CurrentThread.ManagedThreadId);
            await tcs.Task;
            Console.WriteLine("Continuation called on thread ID: {0}", Thread.CurrentThread.ManagedThreadId);
        });
        await Task.Delay(100);
        Console.WriteLine("SetResult called on thread ID: {0}", Thread.CurrentThread.ManagedThreadId);
        tcs.SetResult("done");
        
        await task;
    }
}

Example output:

Awaiting task on thread ID: 11
SetResult called on thread ID: 19
Continuation called on thread ID: 9

If not, is there any other way to await the execution of the continuations of the tcs.Task after calling SetResult?

Edit

The behavior would be similar to calling Invoke on an event, where each delegate will be executed on the same thread before the Invoke call returns. In the case a continuation awaits, the expected behavior would be for SetResult to return as well, and not wait for the full completion of the continuation.

ANisus
  • 74,460
  • 29
  • 162
  • 158
  • I do not think such thing would be possible, nor desirable. If you could force continuations to run on the same thread you could very easily cause thread safety issues, for example if you start updating the UI on a thread pool thread. And how are you supposed to determine when a continuation is 'done'? If your continuation entered a Wait it could easily cause deadlocks. – JonasH Jun 20 '22 at 15:15
  • 1
    And your specific example could easily be solved by just awaiting the task directly after the `SetResult`. – JonasH Jun 20 '22 at 15:18
  • @JonasH It surely wouldn't be desirable in most cases. But in some, it might. And since it _can_ run on the same thread, the question was if I can make it _always_ do that. And if a continuation awaits, then I mean for SetResult to return (I'll edit the question to clarify). And yes, in this example I can just wait, but in the real case, I don't have access to the continuation task. – ANisus Jun 21 '22 at 07:11
  • Doesn't it [happen anyway](https://stackoverflow.com/a/12694831/11683)? – GSerg Jun 21 '22 at 07:26
  • @GSerg Not always, I'm afraid. I added example output for the above code to show threads they might run on. – ANisus Jun 21 '22 at 07:42
  • If you don't have access to the task, I assume you can't control [all these things](https://stackoverflow.com/a/39307345/11683) either, and can't provide a [synchronization context](https://stackoverflow.com/a/42771609/11683). – GSerg Jun 21 '22 at 08:03
  • @TheodorZoulias The `invoke` variable served no other purpose than to clarify the example, so I deleted it to avoid having it become a distraction; the question is still about forcing the continuation to run on the same thread, thus becoming synchronous. I don't see the contradiction you mention though, and the behavior I am looking for is not as much a requirement as how I expect it to run when being called on a single thread. I edited the text, giving event invocation as an example. Hope it clarifies. – ANisus Jun 21 '22 at 10:47
  • @GSerg A synchronization context might be the solution! But it seems, from all the comments, that there is no simple solution to what I asked. I will likely rewrite the structure to use a callback delegate for the cases where synchronous execution is required. – ANisus Jun 21 '22 at 10:53
  • *"In the case a continuation awaits,"* -- Do you mean in case there was a second `await` after the `await tcs.Task;`, inside the `Task.Run` async delegate? – Theodor Zoulias Jun 21 '22 at 11:36
  • 1
    @TheodorZoulias Yes, exactly. So, `Console.WriteLine("Continuation called...` is called on the same thread up until next `await`, if we have one. – ANisus Jun 21 '22 at 14:05
  • Btw [running your example](https://dotnetfiddle.net/m24uV2) on dotnetfiddle.net (which runs .NET 6 on [Linux-Ubuntu](https://dotnetfiddle.net/WIYcGi)) yields these thread IDs: 4-4-4. Replacing the `await Task.Delay(100)` with `Thread.Sleep(100)` yields these IDs: 4-1-1. So the `TaskContinuationOptions.ExecuteSynchronously` configuration seems to be honored. – Theodor Zoulias Jun 21 '22 at 17:00

0 Answers0