Home > Software design >  Why will the synchronous continuations no longer be executed synchronously?
Why will the synchronous continuations no longer be executed synchronously?

Time:12-26

In the following article, Stephen Toub describes how to build a ManualResetEvent using the TPL. In particular, he states that in the following method:

public void Set()
{
    var tcs = m_tcs;
    Task.Factory.StartNew(
        s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
        tcs,
        CancellationToken.None,
        TaskCreationOptions.PreferFairness,
        TaskScheduler.Default);
    tcs.Task.Wait();
}

the tcs.Task.Wait() will "block until the task being completed is actually finished (not including the task’s synchronous continuations, just the task itself)". Why won't we block on any synchronous continuations? That is, what will prevent any synchronous continuations on any Tasks awaiting tcs.Task's completion from executing synchronously?

CodePudding user response:

He means that if you do is this way:

public void Set()
{
    m_tsc.TrySetResult(true);
}

Then all synchronous continuations of TaskCompletionSource Task will run synchronously on this same thread, blocking Set method for the duration.

However if you do it the way described in your question, then all those continuations will still run, but on thread pool thread, because that's where you execute TrySetResult. As soon as TaskCompletionSource task completes - your tcs.Task.Wait() will return, before synchronous continuations will run. So in effect, Set method will not block waiting for them to complete.

CodePudding user response:

This is because the work in ContinueWith is a different task. All ContinueWith variants say:

Returns

A new continuation Task.

and in the Remarks we can read:

Remarks

The returned Task will not be scheduled for execution until the current task has completed.


BTW. These days ContinueWith is discouraged and await is preferred in almost all cases.

CodePudding user response:

the tcs.Task.Wait() will "block until the task being completed is actually finished (not including the task’s synchronous continuations, just the task itself)". Why won't we block on any synchronous continuations? That is, what will prevent any synchronous continuations on any Tasks awaiting tcs.Task's completion from executing synchronously?

Your question is about Tasks in general, not about the TaskCompletionSource.Task in particular. The answer is that when an incomplete Task is waited, the Wait is performed by a ManualResetEventSlim, which is Set by a task continuation, which is prioritized in front of all other continuations that might be attached previously. Here is a part of the private SpinThenBlockingWait method, from the Task.cs source code:

private bool SpinThenBlockingWait(int millisecondsTimeout,
    CancellationToken cancellationToken)
{
    bool infiniteWait = millisecondsTimeout == Timeout.Infinite;
    uint startTimeTicks = infiniteWait ? 0 : (uint)Environment.TickCount;
    bool returnValue = SpinWait(millisecondsTimeout);
    if (!returnValue)
    {
        var mres = new SetOnInvokeMres();
        try
        {
            AddCompletionAction(mres, addBeforeOthers: true);
            if (infiniteWait)
            {
                bool notifyWhenUnblocked = ThreadPool.NotifyThreadBlocked();
                try
                {
                    returnValue = mres.Wait(Timeout.Infinite, cancellationToken);
                    //...

This method is invoked internally by the Wait, when the task is not complete at the moment, and the current thread must be blocked. The SetOnInvokeMres is a small class that inherits from the ManualResetEventSlim:

private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction
{
    internal SetOnInvokeMres() : base(false, 0) { }
    public void Invoke(Task completingTask) { Set(); }
    public bool InvokeMayRunArbitraryCode => false;
}

The interesting point is the addBeforeOthers: true argument. This ensures that the ManualResetEventSlim will be signaled before invoking any continuation that is associated with user-supplied code. The Task Parallel Library exposes no public API that allows to attach a continuation with addBeforeOthers: true. Actually this parameter seems to exist exclusively for supporting the signaling of ManualResetEventSlims with priority.

  • Related