1

Here's a significantly reduced test case from a piece of code I'm working on:

var i = 0;

var taskCompletionSource = new TaskCompletionSource<object>();
var task = taskCompletionSource.Task;

Task.Run(async () => 
{
    await task;
    i = 1;
});

// Synchronously complete `task`
taskCompletionSource.SetResult(null);

// ???

Console.WriteLine(i);

Assuming that this code runs in the context of an async method, what should replace // ??? to ensure that this code prints 1 rather than 0?

I believe I understand why as written the program will always print 0 -- the code is executing synchronously and nothing has yielded to the scheduler. But I am surprised to learn that

await Task.Yield();

doesn't suffice. Somewhat ironically (but perfectly understandably, given that it involves no asynchronous execution) neither does

await task;

On the other hand,

await Task.Delay(1);

does seem to be enough, but I'm not clear on whether that's a guarantee or an accident of timing.

To ask the question one more way: is there any (reasonable) code I can write which will guarantee that all continuations for task have run before proceeding?

Nick
  • 389
  • 2
  • 10
  • The written code does not work because you do not wait for the task that is created from Task.Run(...). You complete `task`, but `Console.WriteLine(i)`is being executed before `i = 1`. But I am unsure of what you are trying to do. See [fiddler](https://dotnetfiddle.net/lUAWix) – Michael Nov 02 '18 at 23:18
  • The reason `await Task.Delay(1);` works is probably because it forces a thread shift and therefore the task will be executed before `Console.Writeline`. – Michael Nov 02 '18 at 23:24
  • As an aside. This is a terrible pattern to use. If the body of the inner method returns some data, you should use `return i;` instead. In that case the initialization will be done in the inner body. This concept is called ENCAPULATION and is one of the four pillars of OOP. – Aron Nov 03 '18 at 17:18
  • Closing over `i` is simply an example to illustrate the question, but thank you. – Nick Nov 06 '18 at 05:28

2 Answers2

2

Can I guarantee runnable task continuations have been run?

By awaiting them.

var i = 0;

var taskCompletionSource = new TaskCompletionSource<object>();
var task = taskCompletionSource.Task;

var continuation = Task.Run(async () => 
{
    await task;
    i = 1;
});

// Synchronously complete `task`
taskCompletionSource.SetResult(null);

// wait for the continuation
await continuation;

// ouputs 1
Console.WriteLine(i);

That works if you're withing an asynchronous method. If you're not, make it asynchronous. You can technically block, too, (.Wait() instead of await) but that invites deadlocks, so be careful.

Haukinger
  • 10,420
  • 2
  • 15
  • 28
0

Assuming that this code runs in the context of an async method

var i = 0;

await Task.Run(async () => 
{
    await task;
    i = 1;
});

Console.WriteLine(i);

or

var i = 0;

await task;
i = 1;

Console.WriteLine(i);
Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
  • This does not answer the question (`Assuming that this code runs in the context of an async method, what should replace // ??? to ensure that this code prints 1 rather than 0?`) – Haukinger Nov 04 '18 at 11:26
  • Nothing wrong essentially, but it's a rewrite of his code that may not be possible, because he wants to do something between `Task.Run` and `SetResult`. – Haukinger Nov 05 '18 at 09:33
  • Where is that written in the question? – Paulo Morgado Nov 05 '18 at 13:16
  • `significantly reduced test case` and `what should replace // ??? to ensure that this code prints 1 rather than 0?` (the latter in bold). – Haukinger Nov 05 '18 at 14:45