1 // ==++== 2 // 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // 5 // ==--== 6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ 7 // 8 // AsyncMethodBuilder.cs 9 // 10 // <OWNER>Microsoft</OWNER> 11 // 12 // Compiler-targeted types that build tasks for use as the return types of asynchronous methods. 13 // 14 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 15 16 using System; 17 using System.Collections.Concurrent; 18 using System.Collections.Generic; 19 using System.Diagnostics; 20 using System.Diagnostics.CodeAnalysis; 21 using System.Diagnostics.Contracts; 22 using System.Runtime.ExceptionServices; 23 using System.Security; 24 using System.Security.Permissions; 25 using System.Threading; 26 using System.Threading.Tasks; 27 28 #if FEATURE_COMINTEROP 29 using System.Runtime.InteropServices.WindowsRuntime; 30 #endif // FEATURE_COMINTEROP 31 32 namespace System.Runtime.CompilerServices 33 { 34 /// <summary> 35 /// Provides a builder for asynchronous methods that return void. 36 /// This type is intended for compiler use only. 37 /// </summary> 38 [HostProtection(Synchronization = true, ExternalThreading = true)] 39 public struct AsyncVoidMethodBuilder 40 { 41 /// <summary>The synchronization context associated with this operation.</summary> 42 private SynchronizationContext m_synchronizationContext; 43 /// <summary>State related to the IAsyncStateMachine.</summary> 44 private AsyncMethodBuilderCore m_coreState; // mutable struct: must not be readonly 45 /// <summary>Task used for debugging and logging purposes only. Lazily initialized.</summary> 46 private Task m_task; 47 48 /// <summary>Initializes a new <see cref="AsyncVoidMethodBuilder"/>.</summary> 49 /// <returns>The initialized <see cref="AsyncVoidMethodBuilder"/>.</returns> CreateSystem.Runtime.CompilerServices.AsyncVoidMethodBuilder50 public static AsyncVoidMethodBuilder Create() 51 { 52 // Capture the current sync context. If there isn't one, use the dummy s_noContextCaptured 53 // instance; this allows us to tell the state of no captured context apart from the state 54 // of an improperly constructed builder instance. 55 SynchronizationContext sc = SynchronizationContext.CurrentNoFlow; 56 if (sc != null) 57 sc.OperationStarted(); 58 return new AsyncVoidMethodBuilder() { m_synchronizationContext = sc }; 59 } 60 61 /// <summary>Initiates the builder's execution with the associated state machine.</summary> 62 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 63 /// <param name="stateMachine">The state machine instance, passed by reference.</param> 64 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception> 65 [SecuritySafeCritical] 66 [DebuggerStepThrough] 67 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 68 { 69 // See comment on AsyncMethodBuilderCore.Start 70 // AsyncMethodBuilderCore.Start(ref stateMachine); 71 72 if (stateMachine == null) throw new ArgumentNullException("stateMachine"); Contract.EndContractBlockSystem.Runtime.CompilerServices.AsyncVoidMethodBuilder73 Contract.EndContractBlock(); 74 75 // Run the MoveNext method within a copy-on-write ExecutionContext scope. 76 // This allows us to undo any ExecutionContext changes made in MoveNext, 77 // so that they won't "leak" out of the first await. 78 79 ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher); RuntimeHelpers.PrepareConstrainedRegionsSystem.Runtime.CompilerServices.AsyncVoidMethodBuilder80 RuntimeHelpers.PrepareConstrainedRegions(); 81 try 82 { 83 ExecutionContext.EstablishCopyOnWriteScope(ref ecs); 84 stateMachine.MoveNext(); 85 } 86 finally 87 { 88 ecs.Undo(); 89 } 90 } 91 92 /// <summary>Associates the builder with the state machine it represents.</summary> 93 /// <param name="stateMachine">The heap-allocated state machine object.</param> 94 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception> 95 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception> SetStateMachine(IAsyncStateMachine stateMachine)96 public void SetStateMachine(IAsyncStateMachine stateMachine) 97 { 98 m_coreState.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore 99 } 100 101 /// <summary> 102 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. 103 /// </summary> 104 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam> 105 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 106 /// <param name="awaiter">The awaiter.</param> 107 /// <param name="stateMachine">The state machine.</param> 108 public void AwaitOnCompleted<TAwaiter, TStateMachine>( 109 ref TAwaiter awaiter, ref TStateMachine stateMachine) 110 where TAwaiter : INotifyCompletion 111 where TStateMachine : IAsyncStateMachine 112 { 113 try 114 { 115 AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null; 116 var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize); 117 Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action."); 118 119 // If this is our first await, such that we've not yet boxed the state machine, do so now. 120 if (m_coreState.m_stateMachine == null) 121 { 122 if (AsyncCausalityTracer.LoggingOn) 123 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0); 124 125 // Box the state machine, then tell the boxed instance to call back into its own builder, 126 // so we can cache the boxed reference. 127 Contract.Assert(!Object.ReferenceEquals((object)stateMachine, (object)stateMachine), "Expected an unboxed state machine reference"); 128 m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null); 129 } 130 131 awaiter.OnCompleted(continuation); 132 } 133 catch (Exception exc) 134 { 135 // Prevent exceptions from leaking to the call site, which could 136 // then allow multiple flows of execution through the same async method 137 // if the awaiter had already scheduled the continuation by the time 138 // the exception was thrown. We propagate the exception on the 139 // ThreadPool because we can trust it to not throw, unlike 140 // if we were to go to a user-supplied SynchronizationContext, 141 // whose Post method could easily throw. 142 AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null); 143 } 144 } 145 146 /// <summary> 147 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. 148 /// </summary> 149 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam> 150 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 151 /// <param name="awaiter">The awaiter.</param> 152 /// <param name="stateMachine">The state machine.</param> 153 [SecuritySafeCritical] 154 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( 155 ref TAwaiter awaiter, ref TStateMachine stateMachine) 156 where TAwaiter : ICriticalNotifyCompletion 157 where TStateMachine : IAsyncStateMachine 158 { 159 try 160 { 161 AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null; 162 var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize); 163 Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action."); 164 165 // If this is our first await, such that we've not yet boxed the state machine, do so now. 166 if (m_coreState.m_stateMachine == null) 167 { 168 if (AsyncCausalityTracer.LoggingOn) 169 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Task.Id, "Async: " + stateMachine.GetType().Name, 0); 170 171 // Box the state machine, then tell the boxed instance to call back into its own builder, 172 // so we can cache the boxed reference. 173 Contract.Assert(!Object.ReferenceEquals((object)stateMachine, (object)stateMachine), "Expected an unboxed state machine reference"); 174 m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null); 175 } 176 177 awaiter.UnsafeOnCompleted(continuation); 178 } 179 catch (Exception e) 180 { 181 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null); 182 } 183 } 184 185 /// <summary>Completes the method builder successfully.</summary> SetResult()186 public void SetResult() 187 { 188 if (AsyncCausalityTracer.LoggingOn) 189 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Completed); 190 191 if (m_synchronizationContext != null) 192 { 193 NotifySynchronizationContextOfCompletion(); 194 } 195 } 196 197 /// <summary>Faults the method builder with an exception.</summary> 198 /// <param name="exception">The exception that is the cause of this fault.</param> 199 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception> 200 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception> SetException(Exception exception)201 public void SetException(Exception exception) 202 { 203 if (exception == null) throw new ArgumentNullException("exception"); 204 Contract.EndContractBlock(); 205 206 if (AsyncCausalityTracer.LoggingOn) 207 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Task.Id, AsyncCausalityStatus.Error); 208 209 if (m_synchronizationContext != null) 210 { 211 // If we captured a synchronization context, Post the throwing of the exception to it 212 // and decrement its outstanding operation count. 213 try 214 { 215 AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: m_synchronizationContext); 216 } 217 finally 218 { 219 NotifySynchronizationContextOfCompletion(); 220 } 221 } 222 else 223 { 224 // Otherwise, queue the exception to be thrown on the ThreadPool. This will 225 // result in a crash unless legacy exception behavior is enabled by a config 226 // file or a CLR host. 227 AsyncMethodBuilderCore.ThrowAsync(exception, targetContext: null); 228 } 229 } 230 231 /// <summary>Notifies the current synchronization context that the operation completed.</summary> NotifySynchronizationContextOfCompletion()232 private void NotifySynchronizationContextOfCompletion() 233 { 234 Contract.Assert(m_synchronizationContext != null, "Must only be used with a non-null context."); 235 try 236 { 237 m_synchronizationContext.OperationCompleted(); 238 } 239 catch (Exception exc) 240 { 241 // If the interaction with the SynchronizationContext goes awry, 242 // fall back to propagating on the ThreadPool. 243 AsyncMethodBuilderCore.ThrowAsync(exc, targetContext: null); 244 } 245 } 246 247 // This property lazily instantiates the Task in a non-thread-safe manner. 248 private Task Task 249 { 250 get 251 { 252 if (m_task == null) m_task = new Task(); 253 return m_task; 254 } 255 } 256 257 /// <summary> 258 /// Gets an object that may be used to uniquely identify this builder to the debugger. 259 /// </summary> 260 /// <remarks> 261 /// This property lazily instantiates the ID in a non-thread-safe manner. 262 /// It must only be used by the debugger and AsyncCausalityTracer in a single-threaded manner. 263 /// </remarks> 264 private object ObjectIdForDebugger { get { return this.Task; } } 265 } 266 267 /// <summary> 268 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task"/>. 269 /// This type is intended for compiler use only. 270 /// </summary> 271 /// <remarks> 272 /// AsyncTaskMethodBuilder is a value type, and thus it is copied by value. 273 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, 274 /// or else the copies may end up building distinct Task instances. 275 /// </remarks> 276 [HostProtection(Synchronization = true, ExternalThreading = true)] 277 public struct AsyncTaskMethodBuilder 278 { 279 /// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary> 280 private readonly static Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask; 281 282 /// <summary>The generic builder object to which this non-generic instance delegates.</summary> 283 private AsyncTaskMethodBuilder<VoidTaskResult> m_builder; // mutable struct: must not be readonly 284 285 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary> 286 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns> CreateAsyncTaskMethodBuilder287 public static AsyncTaskMethodBuilder Create() 288 { 289 return default(AsyncTaskMethodBuilder); 290 // Note: If ATMB<T>.Create is modified to do any initialization, this 291 // method needs to be updated to do m_builder = ATMB<T>.Create(). 292 } 293 294 /// <summary>Initiates the builder's execution with the associated state machine.</summary> 295 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 296 /// <param name="stateMachine">The state machine instance, passed by reference.</param> 297 [SecuritySafeCritical] 298 [DebuggerStepThrough] 299 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 300 { 301 // See comment on AsyncMethodBuilderCore.Start 302 // AsyncMethodBuilderCore.Start(ref stateMachine); 303 304 if (stateMachine == null) throw new ArgumentNullException("stateMachine"); Contract.EndContractBlockAsyncTaskMethodBuilder305 Contract.EndContractBlock(); 306 307 // Run the MoveNext method within a copy-on-write ExecutionContext scope. 308 // This allows us to undo any ExecutionContext changes made in MoveNext, 309 // so that they won't "leak" out of the first await. 310 311 ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher); RuntimeHelpers.PrepareConstrainedRegionsAsyncTaskMethodBuilder312 RuntimeHelpers.PrepareConstrainedRegions(); 313 try 314 { 315 ExecutionContext.EstablishCopyOnWriteScope(ref ecs); 316 stateMachine.MoveNext(); 317 } 318 finally 319 { 320 ecs.Undo(); 321 } 322 } 323 324 /// <summary>Associates the builder with the state machine it represents.</summary> 325 /// <param name="stateMachine">The heap-allocated state machine object.</param> 326 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception> 327 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception> SetStateMachine(IAsyncStateMachine stateMachine)328 public void SetStateMachine(IAsyncStateMachine stateMachine) 329 { 330 m_builder.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore 331 } 332 333 /// <summary> 334 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. 335 /// </summary> 336 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam> 337 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 338 /// <param name="awaiter">The awaiter.</param> 339 /// <param name="stateMachine">The state machine.</param> 340 public void AwaitOnCompleted<TAwaiter, TStateMachine>( 341 ref TAwaiter awaiter, ref TStateMachine stateMachine) 342 where TAwaiter : INotifyCompletion 343 where TStateMachine : IAsyncStateMachine 344 { 345 m_builder.AwaitOnCompleted<TAwaiter, TStateMachine>(ref awaiter, ref stateMachine); 346 } 347 348 /// <summary> 349 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. 350 /// </summary> 351 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam> 352 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 353 /// <param name="awaiter">The awaiter.</param> 354 /// <param name="stateMachine">The state machine.</param> 355 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( 356 ref TAwaiter awaiter, ref TStateMachine stateMachine) 357 where TAwaiter : ICriticalNotifyCompletion 358 where TStateMachine : IAsyncStateMachine 359 { 360 m_builder.AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref awaiter, ref stateMachine); 361 } 362 363 /// <summary>Gets the <see cref="System.Threading.Tasks.Task"/> for this builder.</summary> 364 /// <returns>The <see cref="System.Threading.Tasks.Task"/> representing the builder's asynchronous operation.</returns> 365 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception> 366 public Task Task { get { return m_builder.Task; } } 367 368 /// <summary> 369 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the 370 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state. 371 /// </summary> 372 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception> 373 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception> SetResult()374 public void SetResult() 375 { 376 // Accessing AsyncTaskMethodBuilder.s_cachedCompleted is faster than 377 // accessing AsyncTaskMethodBuilder<T>.s_defaultResultTask. 378 m_builder.SetResult(s_cachedCompleted); 379 } 380 381 /// <summary> 382 /// Completes the <see cref="System.Threading.Tasks.Task"/> in the 383 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception. 384 /// </summary> 385 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param> 386 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception> 387 /// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception> 388 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception> SetException(Exception exception)389 public void SetException(Exception exception) { m_builder.SetException(exception); } 390 391 /// <summary> 392 /// Called by the debugger to request notification when the first wait operation 393 /// (await, Wait, Result, etc.) on this builder's task completes. 394 /// </summary> 395 /// <param name="enabled"> 396 /// true to enable notification; false to disable a previously set notification. 397 /// </param> SetNotificationForWaitCompletion(bool enabled)398 internal void SetNotificationForWaitCompletion(bool enabled) 399 { 400 m_builder.SetNotificationForWaitCompletion(enabled); 401 } 402 403 /// <summary> 404 /// Gets an object that may be used to uniquely identify this builder to the debugger. 405 /// </summary> 406 /// <remarks> 407 /// This property lazily instantiates the ID in a non-thread-safe manner. 408 /// It must only be used by the debugger and tracing pruposes, and only in a single-threaded manner 409 /// when no other threads are in the middle of accessing this property or this.Task. 410 /// </remarks> 411 private object ObjectIdForDebugger { get { return this.Task; } } 412 } 413 414 /// <summary> 415 /// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task{TResult}"/>. 416 /// This type is intended for compiler use only. 417 /// </summary> 418 /// <remarks> 419 /// AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value. 420 /// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed, 421 /// or else the copies may end up building distinct Task instances. 422 /// </remarks> 423 [HostProtection(Synchronization = true, ExternalThreading = true)] 424 public struct AsyncTaskMethodBuilder<TResult> 425 { 426 /// <summary>A cached task for default(TResult).</summary> 427 internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult)); 428 429 // WARNING: For performance reasons, the m_task field is lazily initialized. 430 // For correct results, the struct AsyncTaskMethodBuilder<TResult> must 431 // always be used from the same location/copy, at least until m_task is 432 // initialized. If that guarantee is broken, the field could end up being 433 // initialized on the wrong copy. 434 435 /// <summary>State related to the IAsyncStateMachine.</summary> 436 private AsyncMethodBuilderCore m_coreState; // mutable struct: must not be readonly 437 /// <summary>The lazily-initialized built task.</summary> 438 private Task<TResult> m_task; // lazily-initialized: must not be readonly 439 440 /// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary> 441 /// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns> 442 public static AsyncTaskMethodBuilder<TResult> Create() 443 { 444 return default(AsyncTaskMethodBuilder<TResult>); 445 // NOTE: If this method is ever updated to perform more initialization, 446 // ATMB.Create must also be updated to call this Create method. 447 } 448 449 /// <summary>Initiates the builder's execution with the associated state machine.</summary> 450 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 451 /// <param name="stateMachine">The state machine instance, passed by reference.</param> 452 [SecuritySafeCritical] 453 [DebuggerStepThrough] 454 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 455 { 456 // See comment on AsyncMethodBuilderCore.Start 457 // AsyncMethodBuilderCore.Start(ref stateMachine); 458 459 if (stateMachine == null) throw new ArgumentNullException("stateMachine"); 460 Contract.EndContractBlock(); 461 462 // Run the MoveNext method within a copy-on-write ExecutionContext scope. 463 // This allows us to undo any ExecutionContext changes made in MoveNext, 464 // so that they won't "leak" out of the first await. 465 466 ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher); 467 RuntimeHelpers.PrepareConstrainedRegions(); 468 try 469 { 470 ExecutionContext.EstablishCopyOnWriteScope(ref ecs); 471 stateMachine.MoveNext(); 472 } 473 finally 474 { 475 ecs.Undo(); 476 } 477 } 478 479 /// <summary>Associates the builder with the state machine it represents.</summary> 480 /// <param name="stateMachine">The heap-allocated state machine object.</param> 481 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception> 482 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception> 483 public void SetStateMachine(IAsyncStateMachine stateMachine) 484 { 485 m_coreState.SetStateMachine(stateMachine); // argument validation handled by AsyncMethodBuilderCore 486 } 487 488 /// <summary> 489 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. 490 /// </summary> 491 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam> 492 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 493 /// <param name="awaiter">The awaiter.</param> 494 /// <param name="stateMachine">The state machine.</param> 495 public void AwaitOnCompleted<TAwaiter, TStateMachine>( 496 ref TAwaiter awaiter, ref TStateMachine stateMachine) 497 where TAwaiter : INotifyCompletion 498 where TStateMachine : IAsyncStateMachine 499 { 500 try 501 { 502 AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null; 503 var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize); 504 Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action."); 505 506 // If this is our first await, such that we've not yet boxed the state machine, do so now. 507 if (m_coreState.m_stateMachine == null) 508 { 509 // Force the Task to be initialized prior to the first suspending await so 510 // that the original stack-based builder has a reference to the right Task. 511 var builtTask = this.Task; 512 513 // Box the state machine, then tell the boxed instance to call back into its own builder, 514 // so we can cache the boxed reference. 515 Contract.Assert(!Object.ReferenceEquals((object)stateMachine, (object)stateMachine), "Expected an unboxed state machine reference"); 516 m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, builtTask); 517 } 518 519 awaiter.OnCompleted(continuation); 520 } 521 catch (Exception e) 522 { 523 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null); 524 } 525 } 526 527 /// <summary> 528 /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. 529 /// </summary> 530 /// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam> 531 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 532 /// <param name="awaiter">The awaiter.</param> 533 /// <param name="stateMachine">The state machine.</param> 534 [SecuritySafeCritical] 535 public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>( 536 ref TAwaiter awaiter, ref TStateMachine stateMachine) 537 where TAwaiter : ICriticalNotifyCompletion 538 where TStateMachine : IAsyncStateMachine 539 { 540 try 541 { 542 AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null; 543 var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize); 544 Contract.Assert(continuation != null, "GetCompletionAction should always return a valid action."); 545 546 // If this is our first await, such that we've not yet boxed the state machine, do so now. 547 if (m_coreState.m_stateMachine == null) 548 { 549 // Force the Task to be initialized prior to the first suspending await so 550 // that the original stack-based builder has a reference to the right Task. 551 var builtTask = this.Task; 552 553 // Box the state machine, then tell the boxed instance to call back into its own builder, 554 // so we can cache the boxed reference. 555 Contract.Assert(!Object.ReferenceEquals((object)stateMachine, (object)stateMachine), "Expected an unboxed state machine reference"); 556 m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, builtTask); 557 } 558 559 awaiter.UnsafeOnCompleted(continuation); 560 } 561 catch (Exception e) 562 { 563 AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null); 564 } 565 } 566 567 /// <summary>Gets the <see cref="System.Threading.Tasks.Task{TResult}"/> for this builder.</summary> 568 /// <returns>The <see cref="System.Threading.Tasks.Task{TResult}"/> representing the builder's asynchronous operation.</returns> 569 public Task<TResult> Task 570 { 571 get 572 { 573 // Get and return the task. If there isn't one, first create one and store it. 574 var task = m_task; 575 if (task == null) { m_task = task = new Task<TResult>(); } 576 return task; 577 } 578 } 579 580 /// <summary> 581 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the 582 /// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state with the specified result. 583 /// </summary> 584 /// <param name="result">The result to use to complete the task.</param> 585 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception> 586 public void SetResult(TResult result) 587 { 588 // Get the currently stored task, which will be non-null if get_Task has already been accessed. 589 // If there isn't one, get a task and store it. 590 var task = m_task; 591 if (task == null) 592 { 593 m_task = GetTaskForResult(result); 594 Contract.Assert(m_task != null, "GetTaskForResult should never return null"); 595 } 596 // Slow path: complete the existing task. 597 else 598 { 599 if (AsyncCausalityTracer.LoggingOn) 600 AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, task.Id, AsyncCausalityStatus.Completed); 601 602 //only log if we have a real task that was previously created 603 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled) 604 { 605 System.Threading.Tasks.Task.RemoveFromActiveTasks(task.Id); 606 } 607 608 if (!task.TrySetResult(result)) 609 { 610 throw new InvalidOperationException(Environment.GetResourceString("TaskT_TransitionToFinal_AlreadyCompleted")); 611 } 612 } 613 } 614 615 /// <summary> 616 /// Completes the builder by using either the supplied completed task, or by completing 617 /// the builder's previously accessed task using default(TResult). 618 /// </summary> 619 /// <param name="completedTask">A task already completed with the value default(TResult).</param> 620 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception> 621 internal void SetResult(Task<TResult> completedTask) 622 { 623 Contract.Requires(completedTask != null, "Expected non-null task"); 624 Contract.Requires(completedTask.Status == TaskStatus.RanToCompletion, "Expected a successfully completed task"); 625 626 // Get the currently stored task, which will be non-null if get_Task has already been accessed. 627 // If there isn't one, store the supplied completed task. 628 var task = m_task; 629 if (task == null) 630 { 631 m_task = completedTask; 632 } 633 else 634 { 635 // Otherwise, complete the task that's there. 636 SetResult(default(TResult)); 637 } 638 } 639 640 /// <summary> 641 /// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the 642 /// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception. 643 /// </summary> 644 /// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param> 645 /// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception> 646 /// <exception cref="System.InvalidOperationException">The task has already completed.</exception> 647 public void SetException(Exception exception) 648 { 649 if (exception == null) throw new ArgumentNullException("exception"); 650 Contract.EndContractBlock(); 651 652 653 var task = m_task; 654 if (task == null) 655 { 656 // Get the task, forcing initialization if it hasn't already been initialized. 657 task = this.Task; 658 } 659 660 // If the exception represents cancellation, cancel the task. Otherwise, fault the task. 661 var oce = exception as OperationCanceledException; 662 bool successfullySet = oce != null ? 663 task.TrySetCanceled(oce.CancellationToken, oce) : 664 task.TrySetException(exception); 665 666 // Unlike with TaskCompletionSource, we do not need to spin here until m_task is completed, 667 // since AsyncTaskMethodBuilder.SetException should not be immediately followed by any code 668 // that depends on the task having completely completed. Moreover, with correct usage, 669 // SetResult or SetException should only be called once, so the Try* methods should always 670 // return true, so no spinning would be necessary anyway (the spinning in TCS is only relevant 671 // if another thread won the ---- to complete the task). 672 673 if (!successfullySet) 674 { 675 throw new InvalidOperationException(Environment.GetResourceString("TaskT_TransitionToFinal_AlreadyCompleted")); 676 } 677 } 678 679 /// <summary> 680 /// Called by the debugger to request notification when the first wait operation 681 /// (await, Wait, Result, etc.) on this builder's task completes. 682 /// </summary> 683 /// <param name="enabled"> 684 /// true to enable notification; false to disable a previously set notification. 685 /// </param> 686 /// <remarks> 687 /// This should only be invoked from within an asynchronous method, 688 /// and only by the debugger. 689 /// </remarks> 690 internal void SetNotificationForWaitCompletion(bool enabled) 691 { 692 // Get the task (forcing initialization if not already initialized), and set debug notification 693 this.Task.SetNotificationForWaitCompletion(enabled); 694 } 695 696 /// <summary> 697 /// Gets an object that may be used to uniquely identify this builder to the debugger. 698 /// </summary> 699 /// <remarks> 700 /// This property lazily instantiates the ID in a non-thread-safe manner. 701 /// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner 702 /// when no other threads are in the middle of accessing this property or this.Task. 703 /// </remarks> 704 private object ObjectIdForDebugger { get { return this.Task; } } 705 706 /// <summary> 707 /// Gets a task for the specified result. This will either 708 /// be a cached or new task, never null. 709 /// </summary> 710 /// <param name="result">The result for which we need a task.</param> 711 /// <returns>The completed task containing the result.</returns> 712 [SecuritySafeCritical] // for JitHelpers.UnsafeCast 713 private Task<TResult> GetTaskForResult(TResult result) 714 { 715 Contract.Ensures( 716 EqualityComparer<TResult>.Default.Equals(result, Contract.Result<Task<TResult>>().Result), 717 "The returned task's Result must return the same value as the specified result value."); 718 719 // The goal of this function is to be give back a cached task if possible, 720 // or to otherwise give back a new task. To give back a cached task, 721 // we need to be able to evaluate the incoming result value, and we need 722 // to avoid as much overhead as possible when doing so, as this function 723 // is invoked as part of the return path from every async method. 724 // Most tasks won't be cached, and thus we need the checks for those that are 725 // to be as close to free as possible. This requires some trickiness given the 726 // lack of generic specialization in .NET. 727 // 728 // Be very careful when modifying this code. It has been tuned 729 // to comply with patterns recognized by both 32-bit and 64-bit JITs. 730 // If changes are made here, be sure to look at the generated assembly, as 731 // small tweaks can have big consequences for what does and doesn't get optimized away. 732 // 733 // Note that this code only ever accesses a static field when it knows it'll 734 // find a cached value, since static fields (even if readonly and integral types) 735 // require special access helpers in this NGEN'd and domain-neutral. 736 737 if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types 738 { 739 // Special case simple value types: 740 // - Boolean 741 // - Byte, SByte 742 // - Char 743 // - Decimal 744 // - Int32, UInt32 745 // - Int64, UInt64 746 // - Int16, UInt16 747 // - IntPtr, UIntPtr 748 // As of .NET 4.5, the (Type)(object)result pattern used below 749 // is recognized and optimized by both 32-bit and 64-bit JITs. 750 751 // For Boolean, we cache all possible values. 752 if (typeof(TResult) == typeof(Boolean)) // only the relevant branches are kept for each value-type generic instantiation 753 { 754 Boolean value = (Boolean)(object)result; 755 Task<Boolean> task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask; 756 return JitHelpers.UnsafeCast<Task<TResult>>(task); // UnsafeCast avoids type check we know will succeed 757 } 758 // For Int32, we cache a range of common values, e.g. [-1,4). 759 else if (typeof(TResult) == typeof(Int32)) 760 { 761 // Compare to constants to avoid static field access if outside of cached range. 762 // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the 763 // lower side, due to positive values being more common than negative as return values. 764 Int32 value = (Int32)(object)result; 765 if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX && 766 value >= AsyncTaskCache.INCLUSIVE_INT32_MIN) 767 { 768 Task<Int32> task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN]; 769 return JitHelpers.UnsafeCast<Task<TResult>>(task); // UnsafeCast avoids a type check we know will succeed 770 } 771 } 772 // For other known value types, we only special-case 0 / default(TResult). 773 else if ( 774 (typeof(TResult) == typeof(UInt32) && default(UInt32) == (UInt32)(object)result) || 775 (typeof(TResult) == typeof(Byte) && default(Byte) == (Byte)(object)result) || 776 (typeof(TResult) == typeof(SByte) && default(SByte) == (SByte)(object)result) || 777 (typeof(TResult) == typeof(Char) && default(Char) == (Char)(object)result) || 778 (typeof(TResult) == typeof(Decimal) && default(Decimal) == (Decimal)(object)result) || 779 (typeof(TResult) == typeof(Int64) && default(Int64) == (Int64)(object)result) || 780 (typeof(TResult) == typeof(UInt64) && default(UInt64) == (UInt64)(object)result) || 781 (typeof(TResult) == typeof(Int16) && default(Int16) == (Int16)(object)result) || 782 (typeof(TResult) == typeof(UInt16) && default(UInt16) == (UInt16)(object)result) || 783 (typeof(TResult) == typeof(IntPtr) && default(IntPtr) == (IntPtr)(object)result) || 784 (typeof(TResult) == typeof(UIntPtr) && default(UIntPtr) == (UIntPtr)(object)result)) 785 { 786 return s_defaultResultTask; 787 } 788 } 789 else if (result == null) // optimized away for value types 790 { 791 return s_defaultResultTask; 792 } 793 794 // No cached task is available. Manufacture a new one for this result. 795 return new Task<TResult>(result); 796 } 797 } 798 799 /// <summary>Provides a cache of closed generic tasks for async methods.</summary> 800 internal static class AsyncTaskCache 801 { 802 // All static members are initialized inline to ensure type is beforefieldinit 803 804 /// <summary>A cached Task{Boolean}.Result == true.</summary> 805 internal readonly static Task<Boolean> TrueTask = CreateCacheableTask(true); 806 /// <summary>A cached Task{Boolean}.Result == false.</summary> 807 internal readonly static Task<Boolean> FalseTask = CreateCacheableTask(false); 808 809 /// <summary>The cache of Task{Int32}.</summary> 810 internal readonly static Task<Int32>[] Int32Tasks = CreateInt32Tasks(); 811 /// <summary>The minimum value, inclusive, for which we want a cached task.</summary> 812 internal const Int32 INCLUSIVE_INT32_MIN = -1; 813 /// <summary>The maximum value, exclusive, for which we want a cached task.</summary> 814 internal const Int32 EXCLUSIVE_INT32_MAX = 9; 815 /// <summary>Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX).</summary> 816 private static Task<Int32>[] CreateInt32Tasks() 817 { 818 Contract.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min"); 819 var tasks = new Task<Int32>[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN]; 820 for (int i = 0; i < tasks.Length; i++) 821 { 822 tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN); 823 } 824 return tasks; 825 } 826 827 /// <summary>Creates a non-disposable task.</summary> 828 /// <typeparam name="TResult">Specifies the result type.</typeparam> 829 /// <param name="result">The result for the task.</param> 830 /// <returns>The cacheable task.</returns> 831 internal static Task<TResult> CreateCacheableTask<TResult>(TResult result) 832 { 833 return new Task<TResult>(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken)); 834 } 835 } 836 837 /// <summary>Holds state related to the builder's IAsyncStateMachine.</summary> 838 /// <remarks>This is a mutable struct. Be very delicate with it.</remarks> 839 internal struct AsyncMethodBuilderCore 840 { 841 /// <summary>A reference to the heap-allocated state machine object associated with this builder.</summary> 842 internal IAsyncStateMachine m_stateMachine; 843 /// <summary>A cached Action delegate used when dealing with a default ExecutionContext.</summary> 844 internal Action m_defaultContextAction; 845 846 // This method is copy&pasted into the public Start methods to avoid size overhead of valuetype generic instantiations. 847 // Ideally, we would build intrinsics to get the raw ref address and raw code address of MoveNext, and just use the shared implementation. 848 #if false 849 /// <summary>Initiates the builder's execution with the associated state machine.</summary> 850 /// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam> 851 /// <param name="stateMachine">The state machine instance, passed by reference.</param> 852 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument is null (Nothing in Visual Basic).</exception> 853 [SecuritySafeCritical] 854 [DebuggerStepThrough] 855 internal static void Start<TStateMachine>(ref TStateMachine stateMachine) 856 where TStateMachine : IAsyncStateMachine 857 { 858 if (stateMachine == null) throw new ArgumentNullException("stateMachine"); 859 Contract.EndContractBlock(); 860 861 // Run the MoveNext method within a copy-on-write ExecutionContext scope. 862 // This allows us to undo any ExecutionContext changes made in MoveNext, 863 // so that they won't "leak" out of the first await. 864 865 Thread currentThread = Thread.CurrentThread; 866 ExecutionContextSwitcher ecs = default(ExecutionContextSwitcher); 867 RuntimeHelpers.PrepareConstrainedRegions(); 868 try 869 { 870 ExecutionContext.EstablishCopyOnWriteScope(ref ecs); 871 stateMachine.MoveNext(); 872 } 873 finally 874 { 875 ecs.Undo(); 876 } 877 } 878 #endif 879 880 /// <summary>Associates the builder with the state machine it represents.</summary> 881 /// <param name="stateMachine">The heap-allocated state machine object.</param> 882 /// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception> 883 /// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception> 884 public void SetStateMachine(IAsyncStateMachine stateMachine) 885 { 886 if (stateMachine == null) throw new ArgumentNullException("stateMachine"); 887 Contract.EndContractBlock(); 888 if (m_stateMachine != null) throw new InvalidOperationException(Environment.GetResourceString("AsyncMethodBuilder_InstanceNotInitialized")); 889 m_stateMachine = stateMachine; 890 } 891 892 /// <summary> 893 /// Gets the Action to use with an awaiter's OnCompleted or UnsafeOnCompleted method. 894 /// On first invocation, the supplied state machine will be boxed. 895 /// </summary> 896 /// <typeparam name="TMethodBuilder">Specifies the type of the method builder used.</typeparam> 897 /// <typeparam name="TStateMachine">Specifies the type of the state machine used.</typeparam> 898 /// <param name="builder">The builder.</param> 899 /// <param name="stateMachine">The state machine.</param> 900 /// <returns>An Action to provide to the awaiter.</returns> 901 [SecuritySafeCritical] 902 internal Action GetCompletionAction(Task taskForTracing, ref MoveNextRunner runnerToInitialize) 903 { 904 Contract.Assert(m_defaultContextAction == null || m_stateMachine != null, 905 "Expected non-null m_stateMachine on non-null m_defaultContextAction"); 906 907 // Alert a listening debugger that we can't make forward progress unless it slips threads. 908 // If we don't do this, and a method that uses "await foo;" is invoked through funceval, 909 // we could end up hooking up a callback to push forward the async method's state machine, 910 // the debugger would then abort the funceval after it takes too long, and then continuing 911 // execution could result in another callback being hooked up. At that point we have 912 // multiple callbacks registered to push the state machine, which could result in bad behavior. 913 Debugger.NotifyOfCrossThreadDependency(); 914 915 // The builder needs to flow ExecutionContext, so capture it. 916 var capturedContext = ExecutionContext.FastCapture(); // ok to use FastCapture as we haven't made any permission demands/asserts 917 918 // If the ExecutionContext is the default context, try to use a cached delegate, creating one if necessary. 919 Action action; 920 MoveNextRunner runner; 921 if (capturedContext != null && capturedContext.IsPreAllocatedDefault) 922 { 923 // Get the cached delegate, and if it's non-null, return it. 924 action = m_defaultContextAction; 925 if (action != null) 926 { 927 Contract.Assert(m_stateMachine != null, "If the delegate was set, the state machine should have been as well."); 928 return action; 929 } 930 931 // There wasn't a cached delegate, so create one and cache it. 932 // The delegate won't be usable until we set the MoveNextRunner's target state machine. 933 runner = new MoveNextRunner(capturedContext, m_stateMachine); 934 935 action = new Action(runner.Run); 936 if (taskForTracing != null) 937 { 938 m_defaultContextAction = action = OutputAsyncCausalityEvents(taskForTracing, action); 939 } 940 else 941 { 942 m_defaultContextAction = action; 943 } 944 } 945 // Otherwise, create an Action that flows this context. The context may be null. 946 // The delegate won't be usable until we set the MoveNextRunner's target state machine. 947 else 948 { 949 runner = new MoveNextRunner(capturedContext, m_stateMachine); 950 action = new Action(runner.Run); 951 952 if (taskForTracing != null) 953 { 954 action = OutputAsyncCausalityEvents(taskForTracing, action); 955 } 956 957 // NOTE: If capturedContext is null, we could create the Action to point directly 958 // to m_stateMachine.MoveNext. However, that follows a much more expensive 959 // delegate creation path. 960 } 961 962 if (m_stateMachine == null) 963 runnerToInitialize = runner; 964 965 return action; 966 } 967 968 private Action OutputAsyncCausalityEvents(Task innerTask, Action continuation) 969 { 970 return CreateContinuationWrapper(continuation, () => 971 { 972 AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, innerTask.Id, CausalitySynchronousWork.Execution); 973 974 // Invoke the original continuation 975 continuation.Invoke(); 976 977 AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution); 978 }, innerTask); 979 } 980 981 internal void PostBoxInitialization(IAsyncStateMachine stateMachine, MoveNextRunner runner, Task builtTask) 982 { 983 if (builtTask != null) 984 { 985 if (AsyncCausalityTracer.LoggingOn) 986 AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, builtTask.Id, "Async: " + stateMachine.GetType().Name, 0); 987 988 if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled) 989 System.Threading.Tasks.Task.AddToActiveTasks(builtTask); 990 } 991 992 m_stateMachine = stateMachine; 993 m_stateMachine.SetStateMachine(m_stateMachine); 994 995 Contract.Assert(runner.m_stateMachine == null, "The runner's state machine should not yet have been populated."); 996 Contract.Assert(m_stateMachine != null, "The builder's state machine field should have been initialized."); 997 998 // Now that we have the state machine, store it into the runner that the action delegate points to. 999 // And return the action. 1000 runner.m_stateMachine = m_stateMachine; // only after this line is the Action delegate usable 1001 } 1002 1003 /// <summary>Throws the exception on the ThreadPool.</summary> 1004 /// <param name="exception">The exception to propagate.</param> 1005 /// <param name="targetContext">The target context on which to propagate the exception. Null to use the ThreadPool.</param> 1006 internal static void ThrowAsync(Exception exception, SynchronizationContext targetContext) 1007 { 1008 // Capture the exception into an ExceptionDispatchInfo so that its 1009 // stack trace and Watson bucket info will be preserved 1010 var edi = ExceptionDispatchInfo.Capture(exception); 1011 1012 // If the user supplied a SynchronizationContext... 1013 if (targetContext != null) 1014 { 1015 try 1016 { 1017 // Post the throwing of the exception to that context, and return. 1018 targetContext.Post(state => ((ExceptionDispatchInfo)state).Throw(), edi); 1019 return; 1020 } 1021 catch (Exception postException) 1022 { 1023 // If something goes horribly wrong in the Post, we'll 1024 // propagate both exceptions on the ThreadPool 1025 edi = ExceptionDispatchInfo.Capture(new AggregateException(exception, postException)); 1026 } 1027 } 1028 1029 // If we have the new error reporting APIs, report this error. Otherwise, Propagate the exception(s) on the ThreadPool 1030 #if FEATURE_COMINTEROP 1031 if (!WindowsRuntimeMarshal.ReportUnhandledError(edi.SourceException)) 1032 #endif // FEATURE_COMINTEROP 1033 { 1034 ThreadPool.QueueUserWorkItem(state => ((ExceptionDispatchInfo)state).Throw(), edi); 1035 } 1036 } 1037 1038 /// <summary>Provides the ability to invoke a state machine's MoveNext method under a supplied ExecutionContext.</summary> 1039 internal sealed class MoveNextRunner 1040 { 1041 /// <summary>The context with which to run MoveNext.</summary> 1042 private readonly ExecutionContext m_context; 1043 /// <summary>The state machine whose MoveNext method should be invoked.</summary> 1044 internal IAsyncStateMachine m_stateMachine; 1045 1046 /// <summary>Initializes the runner.</summary> 1047 /// <param name="context">The context with which to run MoveNext.</param> 1048 [SecurityCritical] // Run needs to be SSC to map to Action delegate, so to prevent misuse, we only allow construction through SC 1049 internal MoveNextRunner(ExecutionContext context, IAsyncStateMachine stateMachine) 1050 { 1051 m_context = context; 1052 m_stateMachine = stateMachine; 1053 } 1054 1055 /// <summary>Invokes MoveNext under the provided context.</summary> 1056 [SecuritySafeCritical] 1057 internal void Run() 1058 { 1059 Contract.Assert(m_stateMachine != null, "The state machine must have been set before calling Run."); 1060 1061 if (m_context != null) 1062 { 1063 try 1064 { 1065 // Get the callback, lazily initializing it as necessary 1066 ContextCallback callback = s_invokeMoveNext; 1067 if (callback == null) { s_invokeMoveNext = callback = InvokeMoveNext; } 1068 1069 // Use the context and callback to invoke m_stateMachine.MoveNext. 1070 ExecutionContext.Run(m_context, callback, m_stateMachine, preserveSyncCtx: true); 1071 } 1072 finally { m_context.Dispose(); } 1073 } 1074 else 1075 { 1076 m_stateMachine.MoveNext(); 1077 } 1078 } 1079 1080 /// <summary>Cached delegate used with ExecutionContext.Run.</summary> 1081 [SecurityCritical] 1082 private static ContextCallback s_invokeMoveNext; // lazily-initialized due to SecurityCritical attribution 1083 1084 /// <summary>Invokes the MoveNext method on the supplied IAsyncStateMachine.</summary> 1085 /// <param name="stateMachine">The IAsyncStateMachine machine instance.</param> 1086 [SecurityCritical] // necessary for ContextCallback in CoreCLR 1087 private static void InvokeMoveNext(object stateMachine) 1088 { 1089 ((IAsyncStateMachine)stateMachine).MoveNext(); 1090 } 1091 } 1092 1093 /// <summary> 1094 /// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes. 1095 /// However debuggers and profilers need more information about what that action is. (In particular what 1096 /// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper 1097 /// which when invoked just does the original action (the invoke action), but also remembers other information 1098 /// (like the action after that (which is also a ContinuationWrapper and thus form a linked list). 1099 // We also store that task if the action is associate with at task. 1100 /// </summary> 1101 private class ContinuationWrapper 1102 { 1103 internal readonly Action m_continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper) 1104 private readonly Action m_invokeAction; // This wrapper is an action that wraps another action, this is that Action. 1105 internal readonly Task m_innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null) 1106 1107 internal ContinuationWrapper(Action continuation, Action invokeAction, Task innerTask) 1108 { 1109 Contract.Requires(continuation != null, "Expected non-null continuation"); 1110 1111 // If we don't have a task, see if our continuation is a wrapper and use that. 1112 if (innerTask == null) 1113 innerTask = TryGetContinuationTask(continuation); 1114 1115 m_continuation = continuation; 1116 m_innerTask = innerTask; 1117 m_invokeAction = invokeAction; 1118 } 1119 1120 internal void Invoke() 1121 { 1122 m_invokeAction(); 1123 } 1124 } 1125 1126 internal static Action CreateContinuationWrapper(Action continuation, Action invokeAction, Task innerTask = null) 1127 { 1128 return new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke; 1129 } 1130 1131 internal static Action TryGetStateMachineForDebugger(Action action) 1132 { 1133 object target = action.Target; 1134 var runner = target as AsyncMethodBuilderCore.MoveNextRunner; 1135 if (runner != null) 1136 { 1137 return new Action(runner.m_stateMachine.MoveNext); 1138 } 1139 1140 var continuationWrapper = target as ContinuationWrapper; 1141 if (continuationWrapper != null) 1142 { 1143 return TryGetStateMachineForDebugger(continuationWrapper.m_continuation); 1144 } 1145 1146 return action; 1147 } 1148 1149 ///<summary> 1150 /// Given an action, see if it is a contiunation wrapper and has a Task associated with it. If so return it (null otherwise) 1151 ///</summary> 1152 internal static Task TryGetContinuationTask(Action action) 1153 { 1154 if (action != null) 1155 { 1156 var asWrapper = action.Target as ContinuationWrapper; 1157 if (asWrapper != null) 1158 return asWrapper.m_innerTask; 1159 } 1160 return null; 1161 } 1162 } 1163 } 1164