1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// YieldAwaitable.cs
//
// <OWNER>Microsoft</OWNER>
//
// Compiler-targeted type for switching back into the current execution context, e.g.
//
// await Task.Yield();
// =====================
// var $awaiter = Task.Yield().GetAwaiter();
// if (!$awaiter.IsCompleted)
// {
// $builder.AwaitUnsafeOnCompleted(ref $awaiter, ref this);
// return;
// Label:
// }
// $awaiter.GetResult();
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
using System;
using System.Security;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Diagnostics.Tracing;
using System.Threading;
using System.Threading.Tasks;
using System.Security.Permissions;
namespace System.Runtime.CompilerServices
{
// NOTE: YieldAwaitable currently has no state; while developers are encouraged to use Task.Yield() to produce one,
// no validation is performed to ensure that the developer isn't doing "await new YieldAwaitable()". Such validation
// would require additional, useless state to be stored, and as this is a type in the CompilerServices namespace, and
// as the above example isn't harmful, we take the cheaper approach of not validating anything.
/// <summary>Provides an awaitable context for switching into a target environment.</summary>
/// <remarks>This type is intended for compiler use only.</remarks>
public readonly struct YieldAwaitable
{
/// <summary>Gets an awaiter for this <see cref="YieldAwaitable"/>.</summary>
/// <returns>An awaiter for this awaitable.</returns>
/// <remarks>This method is intended for compiler user rather than use directly in code.</remarks>
public YieldAwaiter GetAwaiter() { return new YieldAwaiter(); }
/// <summary>Provides an awaiter that switches into a target environment.</summary>
/// <remarks>This type is intended for compiler use only.</remarks>
[HostProtection(Synchronization = true, ExternalThreading = true)]
public readonly struct YieldAwaiter : ICriticalNotifyCompletion
{
/// <summary>Gets whether a yield is not required.</summary>
/// <remarks>This property is intended for compiler user rather than use directly in code.</remarks>
public bool IsCompleted { get { return false; } } // yielding is always required for YieldAwaiter, hence false
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
/// <param name="continuation">The action to invoke asynchronously.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
[SecuritySafeCritical]
public void OnCompleted(Action continuation)
{
QueueContinuation(continuation, flowContext: true);
}
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
/// <param name="continuation">The action to invoke asynchronously.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
[SecurityCritical]
public void UnsafeOnCompleted(Action continuation)
{
QueueContinuation(continuation, flowContext: false);
}
/// <summary>Posts the <paramref name="continuation"/> back to the current context.</summary>
/// <param name="continuation">The action to invoke asynchronously.</param>
/// <param name="flowContext">true to flow ExecutionContext; false if flowing is not required.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="continuation"/> argument is null (Nothing in Visual Basic).</exception>
[SecurityCritical]
private static void QueueContinuation(Action continuation, bool flowContext)
{
// Validate arguments
if (continuation == null) throw new ArgumentNullException("continuation");
Contract.EndContractBlock();
#if !MONO
if (TplEtwProvider.Log.IsEnabled())
{
continuation = OutputCorrelationEtwEvent(continuation);
}
#endif
// Get the current SynchronizationContext, and if there is one,
// post the continuation to it. However, treat the base type
// as if there wasn't a SynchronizationContext, since that's what it
// logically represents.
var syncCtx = SynchronizationContext.CurrentNoFlow;
if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
{
syncCtx.Post(s_sendOrPostCallbackRunAction, continuation);
}
else
{
// If we're targeting the default scheduler, queue to the thread pool, so that we go into the global
// queue. As we're going into the global queue, we might as well use QUWI, which for the global queue is
// just a tad faster than task, due to a smaller object getting allocated and less work on the execution path.
TaskScheduler scheduler = TaskScheduler.Current;
if (scheduler == TaskScheduler.Default)
{
if (flowContext)
{
ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
}
else
{
ThreadPool.UnsafeQueueUserWorkItem(s_waitCallbackRunAction, continuation);
}
}
// We're targeting a custom scheduler, so queue a task.
else
{
Task.Factory.StartNew(continuation, default(CancellationToken), TaskCreationOptions.PreferFairness, scheduler);
}
}
}
#if !MONO
private static Action OutputCorrelationEtwEvent(Action continuation)
{
int continuationId = Task.NewId();
Task currentTask = Task.InternalCurrent;
// fire the correlation ETW event
TplEtwProvider.Log.AwaitTaskContinuationScheduled(TaskScheduler.Current.Id, (currentTask != null) ? currentTask.Id : 0, continuationId);
return AsyncMethodBuilderCore.CreateContinuationWrapper(continuation, () =>
{
var etwLog = TplEtwProvider.Log;
etwLog.TaskWaitContinuationStarted(continuationId);
// ETW event for Task Wait End.
Guid prevActivityId = new Guid();
// Ensure the continuation runs under the correlated activity ID generated above
if (etwLog.TasksSetActivityIds)
EventSource.SetCurrentThreadActivityId(TplEtwProvider.CreateGuidForTaskID(continuationId), out prevActivityId);
// Invoke the original continuation provided to OnCompleted.
continuation();
// Restore activity ID
if (etwLog.TasksSetActivityIds)
EventSource.SetCurrentThreadActivityId(prevActivityId);
etwLog.TaskWaitContinuationComplete(continuationId);
});
}
#endif
/// <summary>WaitCallback that invokes the Action supplied as object state.</summary>
private static readonly WaitCallback s_waitCallbackRunAction = RunAction;
/// <summary>SendOrPostCallback that invokes the Action supplied as object state.</summary>
private static readonly SendOrPostCallback s_sendOrPostCallbackRunAction = RunAction;
/// <summary>Runs an Action delegate provided as state.</summary>
/// <param name="state">The Action delegate to invoke.</param>
private static void RunAction(object state) { ((Action)state)(); }
/// <summary>Ends the await operation.</summary>
public void GetResult() {} // Nop. It exists purely because the compiler pattern demands it.
}
}
}
|