File: src\runtime\src\coreclr\System.Private.CoreLib\src\System\Runtime\CompilerServices\RuntimeAsyncTaskContinuation.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace System.Runtime.CompilerServices
{
    internal sealed unsafe class RuntimeAsyncTaskContinuation : Continuation, IThreadPoolWorkItem
    {
        internal Task? Task;
        internal Task? RuntimeAsyncTask;
        private delegate*<Task, ref byte, void> _getResult;
        internal object? ContinuationContext;

        public RuntimeAsyncTaskContinuation()
        {
            ResumeInfo = (ResumeInfo*)Unsafe.AsPointer(in TaskContinuationResume.ResumeInfo);
        }

        void IThreadPoolWorkItem.Execute()
        {
            Execute(canInline: true);
        }

        internal void Execute(bool canInline)
        {
            Debug.Assert(RuntimeAsyncTask != null);

            if (((Flags & ContinuationFlags.AllContinuationFlags) == 0) || !QueueIfNecessary(canInline))
            {
                if (canInline)
                {
                    RuntimeAsyncTask.ExecuteDirectly(null);
                }
                else
                {
                    ThreadPool.UnsafeQueueUserWorkItemInternal(RuntimeAsyncTask, preferLocal: true);
                }
            }
        }

        private bool QueueIfNecessary(bool canInline)
        {
            Debug.Assert(RuntimeAsyncTask != null);

            if ((Flags & ContinuationFlags.ContinueOnThreadPool) != 0)
            {
                Flags &= ~ContinuationFlags.ContinueOnThreadPool;
                SynchronizationContext? ctx = Thread.CurrentThread._synchronizationContext;
                if (ctx == null || ctx.GetType() == typeof(SynchronizationContext))
                {
                    TaskScheduler? sched = TaskScheduler.InternalCurrent;
                    if (sched == null || sched == TaskScheduler.Default)
                    {
                        // Can inline
                        return false;
                    }
                }

                ThreadPool.UnsafeQueueUserWorkItemInternal(RuntimeAsyncTask, preferLocal: true);
                return true;
            }

            if ((Flags & ContinuationFlags.ContinueOnCapturedSynchronizationContext) != 0)
            {
                Flags &= ~ContinuationFlags.ContinueOnCapturedSynchronizationContext;

                object? continuationContext = ContinuationContext;
                Debug.Assert(continuationContext is SynchronizationContext { });
                SynchronizationContext continuationSyncCtx = (SynchronizationContext)continuationContext;

                if (canInline && continuationSyncCtx == Thread.CurrentThread._synchronizationContext)
                {
                    return false;
                }

                try
                {
                    continuationSyncCtx.Post(TaskContinuationResume.s_postCallback, RuntimeAsyncTask);
                }
                catch (Exception ex)
                {
                    Task.ThrowAsync(ex, targetContext: null);
                }

                return true;
            }

            if ((Flags & ContinuationFlags.ContinueOnCapturedTaskScheduler) != 0)
            {
                Flags &= ~ContinuationFlags.ContinueOnCapturedTaskScheduler;
                object? continuationContext = ContinuationContext;
                Debug.Assert(continuationContext is TaskScheduler { });
                TaskScheduler sched = (TaskScheduler)continuationContext;

                TaskSchedulerAwaitTaskContinuation.RunOrScheduleAction((Action)RuntimeAsyncTask.m_action!, sched, capturedContext: null, allowInlining: canInline);

                return true;
            }

            return false;
        }

        public void GetResult(ref byte returnValue)
        {
            Debug.Assert(Task != null);

            // Avoid retaining the task. The call below may throw.
            Task task = Task;
            Task = null;

            _getResult(task, ref returnValue);
        }

        public void Initialize(Task task)
        {
            Task = task;
            _getResult = &GetResult;
        }

        public void Initialize<T>(Task<T> task)
        {
            Task = task;
            _getResult = &GetResult<T>;
        }

        private static void GetResult(Task task, ref byte result)
        {
            TaskAwaiter.ValidateEnd(task);
        }

        private static void GetResult<T>(Task task, ref byte result)
        {
            Debug.Assert(task is Task<T>);

            Task<T> taskOfT = Unsafe.As<Task, Task<T>>(ref task);
            TaskAwaiter.ValidateEnd(taskOfT);
            Unsafe.As<byte, T>(ref result) = taskOfT.ResultOnSuccess;
        }

        private static class TaskContinuationResume
        {
            [FixedAddressValueType]
            public static readonly ResumeInfo ResumeInfo = new ResumeInfo
            {
                DiagnosticIP = null,
                Resume = &ResumeTaskContinuation,
            };

            [StackTraceHidden]
            private static Continuation? ResumeTaskContinuation(Continuation cont, ref byte result)
            {
                var taskCont = (RuntimeAsyncTaskContinuation)cont;
                taskCont.Next = null;
                taskCont.RuntimeAsyncTask = null;
                taskCont.ContinuationContext = null;

                Debug.Assert((taskCont.Flags & ContinuationFlags.AllContinuationFlags) == 0);

                AsyncHelpers.ReturnTaskContinuation(taskCont);

                taskCont.GetResult(ref result);
                return null;
            }

            internal static readonly SendOrPostCallback s_postCallback = static state =>
            {
                Debug.Assert(state is Task);
                ((Task)state).ExecuteDirectly(null);
            };
        }
    }
}