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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
//
//
// Compiler-targeted types that build tasks for use as the return types of asynchronous methods.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using Internal.Runtime.CompilerServices;
namespace System.Runtime.CompilerServices
{
/// <summary>
/// Provides a builder for asynchronous methods that return void.
/// This type is intended for compiler use only.
/// </summary>
public struct AsyncVoidMethodBuilder
{
/// <summary>The synchronization context associated with this operation.</summary>
private SynchronizationContext _synchronizationContext;
/// <summary>The builder this void builder wraps.</summary>
private AsyncTaskMethodBuilder _builder; // mutable struct: must not be readonly
/// <summary>Initializes a new <see cref="AsyncVoidMethodBuilder"/>.</summary>
/// <returns>The initialized <see cref="AsyncVoidMethodBuilder"/>.</returns>
public static AsyncVoidMethodBuilder Create()
{
SynchronizationContext sc = SynchronizationContext.Current;
sc?.OperationStarted();
#if PROJECTN
// ProjectN's AsyncTaskMethodBuilder.Create() currently does additional debugger-related
// work, so we need to delegate to it.
return new AsyncVoidMethodBuilder() { _synchronizationContext = sc, _builder = AsyncTaskMethodBuilder.Create() };
#else
// _builder should be initialized to AsyncTaskMethodBuilder.Create(), but on coreclr
// that Create() is a nop, so we can just return the default here.
return new AsyncVoidMethodBuilder() { _synchronizationContext = sc };
#endif
}
/// <summary>Initiates the builder's execution with the associated state machine.</summary>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="stateMachine">The state machine instance, passed by reference.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
AsyncMethodBuilderCore.Start(ref stateMachine);
/// <summary>Associates the builder with the state machine it represents.</summary>
/// <param name="stateMachine">The heap-allocated state machine object.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
public void SetStateMachine(IAsyncStateMachine stateMachine) =>
_builder.SetStateMachine(stateMachine);
/// <summary>
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
/// </summary>
/// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="awaiter">The awaiter.</param>
/// <param name="stateMachine">The state machine.</param>
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine =>
_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
/// <summary>
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
/// </summary>
/// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="awaiter">The awaiter.</param>
/// <param name="stateMachine">The state machine.</param>
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine =>
_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
/// <summary>Completes the method builder successfully.</summary>
public void SetResult()
{
if (AsyncCausalityTracer.LoggingOn)
{
AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Completed);
}
// Mark the builder as completed. As this is a void-returning method, this mostly
// doesn't matter, but it can affect things like debug events related to finalization.
_builder.SetResult();
if (_synchronizationContext != null)
{
NotifySynchronizationContextOfCompletion();
}
}
/// <summary>Faults the method builder with an exception.</summary>
/// <param name="exception">The exception that is the cause of this fault.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
public void SetException(Exception exception)
{
if (exception == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
}
if (AsyncCausalityTracer.LoggingOn)
{
AsyncCausalityTracer.TraceOperationCompletion(this.Task, AsyncCausalityStatus.Error);
}
if (_synchronizationContext != null)
{
// If we captured a synchronization context, Post the throwing of the exception to it
// and decrement its outstanding operation count.
try
{
System.Threading.Tasks.Task.ThrowAsync(exception, targetContext: _synchronizationContext);
}
finally
{
NotifySynchronizationContextOfCompletion();
}
}
else
{
// Otherwise, queue the exception to be thrown on the ThreadPool. This will
// result in a crash unless legacy exception behavior is enabled by a config
// file or a CLR host.
System.Threading.Tasks.Task.ThrowAsync(exception, targetContext: null);
}
// The exception was propagated already; we don't need or want to fault the builder, just mark it as completed.
_builder.SetResult();
}
/// <summary>Notifies the current synchronization context that the operation completed.</summary>
private void NotifySynchronizationContextOfCompletion()
{
Debug.Assert(_synchronizationContext != null, "Must only be used with a non-null context.");
try
{
_synchronizationContext.OperationCompleted();
}
catch (Exception exc)
{
// If the interaction with the SynchronizationContext goes awry,
// fall back to propagating on the ThreadPool.
Task.ThrowAsync(exc, targetContext: null);
}
}
/// <summary>Lazily instantiate the Task in a non-thread-safe manner.</summary>
private Task Task => _builder.Task;
/// <summary>
/// Gets an object that may be used to uniquely identify this builder to the debugger.
/// </summary>
/// <remarks>
/// This property lazily instantiates the ID in a non-thread-safe manner.
/// It must only be used by the debugger and AsyncCausalityTracer in a single-threaded manner.
/// </remarks>
internal object ObjectIdForDebugger => _builder.ObjectIdForDebugger;
}
/// <summary>
/// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task"/>.
/// This type is intended for compiler use only.
/// </summary>
/// <remarks>
/// AsyncTaskMethodBuilder is a value type, and thus it is copied by value.
/// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
/// or else the copies may end up building distinct Task instances.
/// </remarks>
public struct AsyncTaskMethodBuilder
{
/// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary>
#if PROJECTN
private static readonly Task<VoidTaskResult> s_cachedCompleted = AsyncTaskCache.CreateCacheableTask<VoidTaskResult>(default(VoidTaskResult));
#else
private static readonly Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask;
#endif
/// <summary>The generic builder object to which this non-generic instance delegates.</summary>
private AsyncTaskMethodBuilder<VoidTaskResult> m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field.
/// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
/// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
public static AsyncTaskMethodBuilder Create() =>
#if PROJECTN
// ProjectN's AsyncTaskMethodBuilder<VoidTaskResult>.Create() currently does additional debugger-related
// work, so we need to delegate to it.
new AsyncTaskMethodBuilder { m_builder = AsyncTaskMethodBuilder<VoidTaskResult>.Create() };
#else
// m_builder should be initialized to AsyncTaskMethodBuilder<VoidTaskResult>.Create(), but on coreclr
// that Create() is a nop, so we can just return the default here.
default;
#endif
/// <summary>Initiates the builder's execution with the associated state machine.</summary>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="stateMachine">The state machine instance, passed by reference.</param>
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
AsyncMethodBuilderCore.Start(ref stateMachine);
/// <summary>Associates the builder with the state machine it represents.</summary>
/// <param name="stateMachine">The heap-allocated state machine object.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
public void SetStateMachine(IAsyncStateMachine stateMachine) =>
m_builder.SetStateMachine(stateMachine);
/// <summary>
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
/// </summary>
/// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="awaiter">The awaiter.</param>
/// <param name="stateMachine">The state machine.</param>
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine =>
m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
/// <summary>
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
/// </summary>
/// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="awaiter">The awaiter.</param>
/// <param name="stateMachine">The state machine.</param>
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine =>
m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
/// <summary>Gets the <see cref="System.Threading.Tasks.Task"/> for this builder.</summary>
/// <returns>The <see cref="System.Threading.Tasks.Task"/> representing the builder's asynchronous operation.</returns>
/// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
public Task Task
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => m_builder.Task;
}
/// <summary>
/// Completes the <see cref="System.Threading.Tasks.Task"/> in the
/// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state.
/// </summary>
/// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
/// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask.
/// <summary>
/// Completes the <see cref="System.Threading.Tasks.Task"/> in the
/// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
/// </summary>
/// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
/// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
public void SetException(Exception exception) => m_builder.SetException(exception);
/// <summary>
/// Called by the debugger to request notification when the first wait operation
/// (await, Wait, Result, etc.) on this builder's task completes.
/// </summary>
/// <param name="enabled">
/// true to enable notification; false to disable a previously set notification.
/// </param>
internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled);
/// <summary>
/// Gets an object that may be used to uniquely identify this builder to the debugger.
/// </summary>
/// <remarks>
/// This property lazily instantiates the ID in a non-thread-safe manner.
/// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
/// when no other threads are in the middle of accessing this property or this.Task.
/// </remarks>
internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger;
}
/// <summary>
/// Provides a builder for asynchronous methods that return <see cref="System.Threading.Tasks.Task{TResult}"/>.
/// This type is intended for compiler use only.
/// </summary>
/// <remarks>
/// AsyncTaskMethodBuilder{TResult} is a value type, and thus it is copied by value.
/// Prior to being copied, one of its Task, SetResult, or SetException members must be accessed,
/// or else the copies may end up building distinct Task instances.
/// </remarks>
public struct AsyncTaskMethodBuilder<TResult>
{
#if !PROJECTN
/// <summary>A cached task for default(TResult).</summary>
internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult));
#endif
/// <summary>The lazily-initialized built task.</summary>
private Task<TResult> m_task; // lazily-initialized: must not be readonly. Debugger depends on the exact name of this field.
/// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
/// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
public static AsyncTaskMethodBuilder<TResult> Create()
{
#if PROJECTN
var result = new AsyncTaskMethodBuilder<TResult>();
if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
{
// This allows the debugger to access m_task directly without evaluating ObjectIdForDebugger for ProjectN
result.InitializeTaskAsStateMachineBox();
}
return result;
#else
// NOTE: If this method is ever updated to perform more initialization,
// other Create methods like AsyncTaskMethodBuilder.Create and
// AsyncValueTaskMethodBuilder.Create must be updated to call this.
return default;
#endif
}
/// <summary>Initiates the builder's execution with the associated state machine.</summary>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="stateMachine">The state machine instance, passed by reference.</param>
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
AsyncMethodBuilderCore.Start(ref stateMachine);
/// <summary>Associates the builder with the state machine it represents.</summary>
/// <param name="stateMachine">The heap-allocated state machine object.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
if (stateMachine == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
if (m_task != null)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.AsyncMethodBuilder_InstanceNotInitialized);
}
// SetStateMachine was originally needed in order to store the boxed state machine reference into
// the boxed copy. Now that a normal box is no longer used, SetStateMachine is also legacy. We need not
// do anything here, and thus assert to ensure we're not calling this from our own implementations.
Debug.Fail("SetStateMachine should not be used.");
}
/// <summary>
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
/// </summary>
/// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="awaiter">The awaiter.</param>
/// <param name="stateMachine">The state machine.</param>
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine
{
try
{
awaiter.OnCompleted(GetStateMachineBox(ref stateMachine).MoveNextAction);
}
catch (Exception e)
{
System.Threading.Tasks.Task.ThrowAsync(e, targetContext: null);
}
}
/// <summary>
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
/// </summary>
/// <typeparam name="TAwaiter">Specifies the type of the awaiter.</typeparam>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="awaiter">The awaiter.</param>
/// <param name="stateMachine">The state machine.</param>
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine
{
IAsyncStateMachineBox box = GetStateMachineBox(ref stateMachine);
// The null tests here ensure that the jit can optimize away the interface
// tests when TAwaiter is a ref type.
if ((null != (object)default(TAwaiter)) && (awaiter is ITaskAwaiter))
{
ref TaskAwaiter ta = ref Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter); // relies on TaskAwaiter/TaskAwaiter<T> having the same layout
TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, continueOnCapturedContext: true);
}
else if ((null != (object)default(TAwaiter)) && (awaiter is IConfiguredTaskAwaiter))
{
ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter ta = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
TaskAwaiter.UnsafeOnCompletedInternal(ta.m_task, box, ta.m_continueOnCapturedContext);
}
else if ((null != (object)default(TAwaiter)) && (awaiter is IStateMachineBoxAwareAwaiter))
{
try
{
((IStateMachineBoxAwareAwaiter)awaiter).AwaitUnsafeOnCompleted(box);
}
catch (Exception e)
{
// Whereas with Task the code that hooks up and invokes the continuation is all local to corelib,
// with ValueTaskAwaiter we may be calling out to an arbitrary implementation of IValueTaskSource
// wrapped in the ValueTask, and as such we protect against errant exceptions that may emerge.
// We don't want such exceptions propagating back into the async method, which can't handle
// exceptions well at that location in the state machine, especially if the exception may occur
// after the ValueTaskAwaiter already successfully hooked up the callback, in which case it's possible
// two different flows of execution could end up happening in the same async method call.
System.Threading.Tasks.Task.ThrowAsync(e, targetContext: null);
}
}
else
{
// The awaiter isn't specially known. Fall back to doing a normal await.
try
{
awaiter.UnsafeOnCompleted(box.MoveNextAction);
}
catch (Exception e)
{
System.Threading.Tasks.Task.ThrowAsync(e, targetContext: null);
}
}
}
/// <summary>Gets the "boxed" state machine object.</summary>
/// <typeparam name="TStateMachine">Specifies the type of the async state machine.</typeparam>
/// <param name="stateMachine">The state machine.</param>
/// <returns>The "boxed" state machine.</returns>
private IAsyncStateMachineBox GetStateMachineBox<TStateMachine>(
ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine
{
ExecutionContext currentContext = ExecutionContext.Capture();
// Check first for the most common case: not the first yield in an async method.
// In this case, the first yield will have already "boxed" the state machine in
// a strongly-typed manner into an AsyncStateMachineBox. It will already contain
// the state machine as well as a MoveNextDelegate and a context. The only thing
// we might need to do is update the context if that's changed since it was stored.
if (m_task is AsyncStateMachineBox<TStateMachine> stronglyTypedBox)
{
if (stronglyTypedBox.Context != currentContext)
{
stronglyTypedBox.Context = currentContext;
}
return stronglyTypedBox;
}
// The least common case: we have a weakly-typed boxed. This results if the debugger
// or some other use of reflection accesses a property like ObjectIdForDebugger or a
// method like SetNotificationForWaitCompletion prior to the first await happening. In
// such situations, we need to get an object to represent the builder, but we don't yet
// know the type of the state machine, and thus can't use TStateMachine. Instead, we
// use the IAsyncStateMachine interface, which all TStateMachines implement. This will
// result in a boxing allocation when storing the TStateMachine if it's a struct, but
// this only happens in active debugging scenarios where such performance impact doesn't
// matter.
if (m_task is AsyncStateMachineBox<IAsyncStateMachine> weaklyTypedBox)
{
// If this is the first await, we won't yet have a state machine, so store it.
if (weaklyTypedBox.StateMachine == null)
{
Debugger.NotifyOfCrossThreadDependency(); // same explanation as with usage below
weaklyTypedBox.StateMachine = stateMachine;
}
// Update the context. This only happens with a debugger, so no need to spend
// extra IL checking for equality before doing the assignment.
weaklyTypedBox.Context = currentContext;
return weaklyTypedBox;
}
// Alert a listening debugger that we can't make forward progress unless it slips threads.
// If we don't do this, and a method that uses "await foo;" is invoked through funceval,
// we could end up hooking up a callback to push forward the async method's state machine,
// the debugger would then abort the funceval after it takes too long, and then continuing
// execution could result in another callback being hooked up. At that point we have
// multiple callbacks registered to push the state machine, which could result in bad behavior.
Debugger.NotifyOfCrossThreadDependency();
// At this point, m_task should really be null, in which case we want to create the box.
// However, in a variety of debugger-related (erroneous) situations, it might be non-null,
// e.g. if the Task property is examined in a Watch window, forcing it to be lazily-intialized
// as a Task<TResult> rather than as an AsyncStateMachineBox. The worst that happens in such
// cases is we lose the ability to properly step in the debugger, as the debugger uses that
// object's identity to track this specific builder/state machine. As such, we proceed to
// overwrite whatever's there anyway, even if it's non-null.
#if CORERT
// DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because
// it will have a copy of all the slots from its parent. It will add another hundred(s) bytes
// per each async method in CoreRT / ProjectN binaries without adding much value. Avoid
// generating this extra code until a better solution is implemented.
var box = new AsyncStateMachineBox<TStateMachine>();
#else
var box = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
CreateDebugFinalizableAsyncStateMachineBox<TStateMachine>() :
new AsyncStateMachineBox<TStateMachine>();
#endif
m_task = box; // important: this must be done before storing stateMachine into box.StateMachine!
box.StateMachine = stateMachine;
box.Context = currentContext;
return box;
}
#if !CORERT
// Avoid forcing the JIT to build DebugFinalizableAsyncStateMachineBox<TStateMachine> unless it's actually needed.
[MethodImpl(MethodImplOptions.NoInlining)]
private static AsyncStateMachineBox<TStateMachine> CreateDebugFinalizableAsyncStateMachineBox<TStateMachine>()
where TStateMachine : IAsyncStateMachine =>
new DebugFinalizableAsyncStateMachineBox<TStateMachine>();
/// <summary>
/// Provides an async state machine box with a finalizer that will fire an EventSource
/// event about the state machine if it's being finalized without having been completed.
/// </summary>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
private sealed class DebugFinalizableAsyncStateMachineBox<TStateMachine> : // SOS DumpAsync command depends on this name
AsyncStateMachineBox<TStateMachine>
where TStateMachine : IAsyncStateMachine
{
~DebugFinalizableAsyncStateMachineBox()
{
// If the state machine is being finalized, something went wrong during its processing,
// e.g. it awaited something that got collected without itself having been completed.
// Fire an event with details about the state machine to help with debugging.
if (!IsCompleted) // double-check it's not completed, just to help minimize false positives
{
TplEventSource.Log.IncompleteAsyncMethod(this);
}
}
}
#endif
/// <summary>A strongly-typed box for Task-based async state machines.</summary>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
private class AsyncStateMachineBox<TStateMachine> : // SOS DumpAsync command depends on this name
Task<TResult>, IAsyncStateMachineBox
where TStateMachine : IAsyncStateMachine
{
/// <summary>Delegate used to invoke on an ExecutionContext when passed an instance of this box type.</summary>
private static readonly ContextCallback s_callback = ExecutionContextCallback;
// Used to initialize s_callback above. We don't use a lambda for this on purpose: a lambda would
// introduce a new generic type behind the scenes that comes with a hefty size penalty in AOT builds.
private static void ExecutionContextCallback(object s)
{
Debug.Assert(s is AsyncStateMachineBox<TStateMachine>);
// Only used privately to pass directly to EC.Run
Unsafe.As<AsyncStateMachineBox<TStateMachine>>(s).StateMachine.MoveNext();
}
/// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
private Action _moveNextAction;
/// <summary>The state machine itself.</summary>
public TStateMachine StateMachine; // mutable struct; do not make this readonly. SOS DumpAsync command depends on this name.
/// <summary>Captured ExecutionContext with which to invoke <see cref="MoveNextAction"/>; may be null.</summary>
public ExecutionContext Context;
/// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
public Action MoveNextAction => _moveNextAction ?? (_moveNextAction = new Action(MoveNext));
internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread) => MoveNext(threadPoolThread);
/// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
public void MoveNext() => MoveNext(threadPoolThread: null);
private void MoveNext(Thread threadPoolThread)
{
Debug.Assert(!IsCompleted);
bool loggingOn = AsyncCausalityTracer.LoggingOn;
if (loggingOn)
{
AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution);
}
ExecutionContext context = Context;
if (context == null)
{
StateMachine.MoveNext();
}
else
{
if (threadPoolThread is null)
{
ExecutionContext.RunInternal(context, s_callback, this);
}
else
{
ExecutionContext.RunFromThreadPoolDispatchLoop(threadPoolThread, context, s_callback, this);
}
}
if (IsCompleted)
{
// Clear out state now that the async method has completed.
// This avoids keeping arbitrary state referenced by lifted locals
// if this Task / state machine box is held onto.
StateMachine = default;
Context = default;
#if !CORERT
// In case this is a state machine box with a finalizer, suppress its finalization
// as it's now complete. We only need the finalizer to run if the box is collected
// without having been completed.
if (AsyncMethodBuilderCore.TrackAsyncMethodCompletion)
{
GC.SuppressFinalize(this);
}
#endif
}
if (loggingOn)
{
AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution);
}
}
/// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
IAsyncStateMachine IAsyncStateMachineBox.GetStateMachineObject() => StateMachine; // likely boxes, only use for debugging
}
/// <summary>Gets the <see cref="System.Threading.Tasks.Task{TResult}"/> for this builder.</summary>
/// <returns>The <see cref="System.Threading.Tasks.Task{TResult}"/> representing the builder's asynchronous operation.</returns>
public Task<TResult> Task
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => m_task ?? InitializeTaskAsPromise();
}
/// <summary>
/// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
/// existence when no state machine is needed, e.g. when the builder is being synchronously completed with
/// an exception, when the builder is being used out of the context of an async method, etc.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private Task<TResult> InitializeTaskAsPromise()
{
Debug.Assert(m_task == null);
return (m_task = new Task<TResult>());
}
/// <summary>
/// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
/// existence due to the debugger trying to enable step-out/step-over/etc. prior to the first await yielding
/// in an async method. In that case, we don't know the actual TStateMachine type, so we're forced to
/// use IAsyncStateMachine instead.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private Task<TResult> InitializeTaskAsStateMachineBox()
{
Debug.Assert(m_task == null);
#if CORERT
// DebugFinalizableAsyncStateMachineBox looks like a small type, but it actually is not because
// it will have a copy of all the slots from its parent. It will add another hundred(s) bytes
// per each async method in CoreRT / ProjectN binaries without adding much value. Avoid
// generating this extra code until a better solution is implemented.
return (m_task = new AsyncStateMachineBox<IAsyncStateMachine>());
#else
return (m_task = AsyncMethodBuilderCore.TrackAsyncMethodCompletion ?
CreateDebugFinalizableAsyncStateMachineBox<IAsyncStateMachine>() :
new AsyncStateMachineBox<IAsyncStateMachine>());
#endif
}
/// <summary>
/// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
/// <see cref="System.Threading.Tasks.TaskStatus">RanToCompletion</see> state with the specified result.
/// </summary>
/// <param name="result">The result to use to complete the task.</param>
/// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
public void SetResult(TResult result)
{
// Get the currently stored task, which will be non-null if get_Task has already been accessed.
// If there isn't one, get a task and store it.
if (m_task == null)
{
m_task = GetTaskForResult(result);
Debug.Assert(m_task != null, $"{nameof(GetTaskForResult)} should never return null");
}
else
{
// Slow path: complete the existing task.
SetExistingTaskResult(result);
}
}
/// <summary>Completes the already initialized task with the specified result.</summary>
/// <param name="result">The result to use to complete the task.</param>
private void SetExistingTaskResult(TResult result)
{
Debug.Assert(m_task != null, "Expected non-null task");
if (AsyncCausalityTracer.LoggingOn || System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
{
LogExistingTaskCompletion();
}
if (!m_task.TrySetResult(result))
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
}
}
/// <summary>Handles logging for the successful completion of an operation.</summary>
private void LogExistingTaskCompletion()
{
Debug.Assert(m_task != null);
if (AsyncCausalityTracer.LoggingOn)
{
AsyncCausalityTracer.TraceOperationCompletion(m_task, AsyncCausalityStatus.Completed);
}
// only log if we have a real task that was previously created
if (System.Threading.Tasks.Task.s_asyncDebuggingEnabled)
{
System.Threading.Tasks.Task.RemoveFromActiveTasks(m_task);
}
}
/// <summary>
/// Completes the builder by using either the supplied completed task, or by completing
/// the builder's previously accessed task using default(TResult).
/// </summary>
/// <param name="completedTask">A task already completed with the value default(TResult).</param>
/// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
internal void SetResult(Task<TResult> completedTask)
{
Debug.Assert(completedTask != null, "Expected non-null task");
Debug.Assert(completedTask.IsCompletedSuccessfully, "Expected a successfully completed task");
// Get the currently stored task, which will be non-null if get_Task has already been accessed.
// If there isn't one, store the supplied completed task.
if (m_task == null)
{
m_task = completedTask;
}
else
{
// Otherwise, complete the task that's there.
SetExistingTaskResult(default);
}
}
/// <summary>
/// Completes the <see cref="System.Threading.Tasks.Task{TResult}"/> in the
/// <see cref="System.Threading.Tasks.TaskStatus">Faulted</see> state with the specified exception.
/// </summary>
/// <param name="exception">The <see cref="System.Exception"/> to use to fault the task.</param>
/// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
public void SetException(Exception exception)
{
if (exception == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
}
// Get the task, forcing initialization if it hasn't already been initialized.
Task<TResult> task = this.Task;
// If the exception represents cancellation, cancel the task. Otherwise, fault the task.
bool successfullySet = exception is OperationCanceledException oce ?
task.TrySetCanceled(oce.CancellationToken, oce) :
task.TrySetException(exception);
// Unlike with TaskCompletionSource, we do not need to spin here until _taskAndStateMachine is completed,
// since AsyncTaskMethodBuilder.SetException should not be immediately followed by any code
// that depends on the task having completely completed. Moreover, with correct usage,
// SetResult or SetException should only be called once, so the Try* methods should always
// return true, so no spinning would be necessary anyway (the spinning in TCS is only relevant
// if another thread completes the task first).
if (!successfullySet)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted);
}
}
/// <summary>
/// Called by the debugger to request notification when the first wait operation
/// (await, Wait, Result, etc.) on this builder's task completes.
/// </summary>
/// <param name="enabled">
/// true to enable notification; false to disable a previously set notification.
/// </param>
/// <remarks>
/// This should only be invoked from within an asynchronous method,
/// and only by the debugger.
/// </remarks>
internal void SetNotificationForWaitCompletion(bool enabled)
{
// Get the task (forcing initialization if not already initialized), and set debug notification
(m_task ?? InitializeTaskAsStateMachineBox()).SetNotificationForWaitCompletion(enabled);
// NOTE: It's important that the debugger use builder.SetNotificationForWaitCompletion
// rather than builder.Task.SetNotificationForWaitCompletion. Even though the latter will
// lazily-initialize the task as well, it'll initialize it to a Task<T> (which is important
// to minimize size for cases where an ATMB is used directly by user code to avoid the
// allocation overhead of a TaskCompletionSource). If that's done prior to the first await,
// the GetMoveNextDelegate code, which needs an AsyncStateMachineBox, will end up creating
// a new box and overwriting the previously created task. That'll change the object identity
// of the task being used for wait completion notification, and no notification will
// ever arrive, breaking step-out behavior when stepping out before the first yielding await.
}
/// <summary>
/// Gets an object that may be used to uniquely identify this builder to the debugger.
/// </summary>
/// <remarks>
/// This property lazily instantiates the ID in a non-thread-safe manner.
/// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
/// when no other threads are in the middle of accessing this or other members that lazily initialize the task.
/// </remarks>
internal object ObjectIdForDebugger => m_task ?? InitializeTaskAsStateMachineBox();
/// <summary>
/// Gets a task for the specified result. This will either
/// be a cached or new task, never null.
/// </summary>
/// <param name="result">The result for which we need a task.</param>
/// <returns>The completed task containing the result.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm
internal static Task<TResult> GetTaskForResult(TResult result)
{
#if PROJECTN
// Currently NUTC does not perform the optimization needed by this method. The result is that
// every call to this method results in quite a lot of work, including many allocations, which
// is the opposite of the intent. For now, let's just return a new Task each time.
// Bug 719350 tracks re-optimizing this in ProjectN.
#else
// The goal of this function is to be give back a cached task if possible,
// or to otherwise give back a new task. To give back a cached task,
// we need to be able to evaluate the incoming result value, and we need
// to avoid as much overhead as possible when doing so, as this function
// is invoked as part of the return path from every async method.
// Most tasks won't be cached, and thus we need the checks for those that are
// to be as close to free as possible. This requires some trickiness given the
// lack of generic specialization in .NET.
//
// Be very careful when modifying this code. It has been tuned
// to comply with patterns recognized by both 32-bit and 64-bit JITs.
// If changes are made here, be sure to look at the generated assembly, as
// small tweaks can have big consequences for what does and doesn't get optimized away.
//
// Note that this code only ever accesses a static field when it knows it'll
// find a cached value, since static fields (even if readonly and integral types)
// require special access helpers in this NGEN'd and domain-neutral.
if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types
{
// Special case simple value types:
// - Boolean
// - Byte, SByte
// - Char
// - Int32, UInt32
// - Int64, UInt64
// - Int16, UInt16
// - IntPtr, UIntPtr
// As of .NET 4.5, the (Type)(object)result pattern used below
// is recognized and optimized by both 32-bit and 64-bit JITs.
// For Boolean, we cache all possible values.
if (typeof(TResult) == typeof(bool)) // only the relevant branches are kept for each value-type generic instantiation
{
bool value = (bool)(object)result;
Task<bool> task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask;
return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids type check we know will succeed
}
// For Int32, we cache a range of common values, e.g. [-1,9).
else if (typeof(TResult) == typeof(int))
{
// Compare to constants to avoid static field access if outside of cached range.
// We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the
// lower side, due to positive values being more common than negative as return values.
int value = (int)(object)result;
if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX &&
value >= AsyncTaskCache.INCLUSIVE_INT32_MIN)
{
Task<int> task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN];
return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids a type check we know will succeed
}
}
// For other known value types, we only special-case 0 / default(TResult).
else if (
(typeof(TResult) == typeof(uint) && default == (uint)(object)result) ||
(typeof(TResult) == typeof(byte) && default(byte) == (byte)(object)result) ||
(typeof(TResult) == typeof(sbyte) && default(sbyte) == (sbyte)(object)result) ||
(typeof(TResult) == typeof(char) && default(char) == (char)(object)result) ||
(typeof(TResult) == typeof(long) && default == (long)(object)result) ||
(typeof(TResult) == typeof(ulong) && default == (ulong)(object)result) ||
(typeof(TResult) == typeof(short) && default(short) == (short)(object)result) ||
(typeof(TResult) == typeof(ushort) && default(ushort) == (ushort)(object)result) ||
(typeof(TResult) == typeof(IntPtr) && default == (IntPtr)(object)result) ||
(typeof(TResult) == typeof(UIntPtr) && default == (UIntPtr)(object)result))
{
return s_defaultResultTask;
}
}
else if (result == null) // optimized away for value types
{
return s_defaultResultTask;
}
#endif
// No cached task is available. Manufacture a new one for this result.
return new Task<TResult>(result);
}
}
/// <summary>Provides a cache of closed generic tasks for async methods.</summary>
internal static class AsyncTaskCache
{
#if !PROJECTN
// All static members are initialized inline to ensure type is beforefieldinit
/// <summary>A cached Task{Boolean}.Result == true.</summary>
internal readonly static Task<bool> TrueTask = CreateCacheableTask(true);
/// <summary>A cached Task{Boolean}.Result == false.</summary>
internal readonly static Task<bool> FalseTask = CreateCacheableTask(false);
/// <summary>The cache of Task{Int32}.</summary>
internal readonly static Task<int>[] Int32Tasks = CreateInt32Tasks();
/// <summary>The minimum value, inclusive, for which we want a cached task.</summary>
internal const int INCLUSIVE_INT32_MIN = -1;
/// <summary>The maximum value, exclusive, for which we want a cached task.</summary>
internal const int EXCLUSIVE_INT32_MAX = 9;
/// <summary>Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX).</summary>
private static Task<int>[] CreateInt32Tasks()
{
Debug.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min");
var tasks = new Task<int>[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN);
}
return tasks;
}
#endif
/// <summary>Creates a non-disposable task.</summary>
/// <typeparam name="TResult">Specifies the result type.</typeparam>
/// <param name="result">The result for the task.</param>
/// <returns>The cacheable task.</returns>
internal static Task<TResult> CreateCacheableTask<TResult>(TResult result) =>
new Task<TResult>(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default);
}
/// <summary>
/// An interface implemented by all <see cref="AsyncTaskMethodBuilder{TResult}.AsyncStateMachineBox{TStateMachine}"/> instances, regardless of generics.
/// </summary>
internal interface IAsyncStateMachineBox
{
/// <summary>Move the state machine forward.</summary>
void MoveNext();
/// <summary>
/// Gets an action for moving forward the contained state machine.
/// This will lazily-allocate the delegate as needed.
/// </summary>
Action MoveNextAction { get; }
/// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
IAsyncStateMachine GetStateMachineObject();
}
/// <summary>Shared helpers for manipulating state related to async state machines.</summary>
internal static class AsyncMethodBuilderCore // debugger depends on this exact name
{
/// <summary>Initiates the builder's execution with the associated state machine.</summary>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="stateMachine">The state machine instance, passed by reference.</param>
[DebuggerStepThrough]
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null) // TStateMachines are generally non-nullable value types, so this check will be elided
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
// enregistrer variables with 0 post-fix so they can be used in registers without EH forcing them to stack
// Capture references to Thread Contexts
Thread currentThread0 = Thread.CurrentThread;
Thread currentThread = currentThread0;
ExecutionContext previousExecutionCtx0 = currentThread0.m_ExecutionContext;
// Store current ExecutionContext and SynchronizationContext as "previousXxx".
// This allows us to restore them and undo any Context changes made in stateMachine.MoveNext
// so that they won't "leak" out of the first await.
ExecutionContext previousExecutionCtx = previousExecutionCtx0;
SynchronizationContext previousSyncCtx = currentThread0.SynchronizationContext;
try
{
stateMachine.MoveNext();
}
finally
{
// Re-enregistrer variables post EH with 1 post-fix so they can be used in registers rather than from stack
SynchronizationContext previousSyncCtx1 = previousSyncCtx;
Thread currentThread1 = currentThread;
// The common case is that these have not changed, so avoid the cost of a write barrier if not needed.
if (previousSyncCtx1 != currentThread1.SynchronizationContext)
{
// Restore changed SynchronizationContext back to previous
currentThread1.SynchronizationContext = previousSyncCtx1;
}
ExecutionContext previousExecutionCtx1 = previousExecutionCtx;
ExecutionContext currentExecutionCtx1 = currentThread1.m_ExecutionContext;
if (previousExecutionCtx1 != currentExecutionCtx1)
{
ExecutionContext.RestoreChangedContextToThread(currentThread1, previousExecutionCtx1, currentExecutionCtx1);
}
}
}
#if !CORERT
/// <summary>Gets whether we should be tracking async method completions for eventing.</summary>
internal static bool TrackAsyncMethodCompletion
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => TplEventSource.Log.IsEnabled(EventLevel.Warning, TplEventSource.Keywords.AsyncMethod);
}
#endif
/// <summary>Gets a description of the state of the state machine object, suitable for debug purposes.</summary>
/// <param name="stateMachine">The state machine object.</param>
/// <returns>A description of the state machine.</returns>
internal static string GetAsyncStateMachineDescription(IAsyncStateMachine stateMachine)
{
Debug.Assert(stateMachine != null);
Type stateMachineType = stateMachine.GetType();
FieldInfo[] fields = stateMachineType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var sb = new StringBuilder();
sb.AppendLine(stateMachineType.FullName);
foreach (FieldInfo fi in fields)
{
sb.AppendLine($" {fi.Name}: {fi.GetValue(stateMachine)}");
}
return sb.ToString();
}
internal static Action CreateContinuationWrapper(Action continuation, Action<Action,Task> invokeAction, Task innerTask) =>
new ContinuationWrapper(continuation, invokeAction, innerTask).Invoke;
/// <summary>This helper routine is targeted by the debugger. Its purpose is to remove any delegate wrappers introduced by
/// the framework that the debugger doesn't want to see.</summary>
#if PROJECTN
[DependencyReductionRoot]
#endif
internal static Action TryGetStateMachineForDebugger(Action action) // debugger depends on this exact name/signature
{
object target = action.Target;
return
target is IAsyncStateMachineBox sm ? sm.GetStateMachineObject().MoveNext :
target is ContinuationWrapper cw ? TryGetStateMachineForDebugger(cw._continuation) :
action;
}
internal static Task TryGetContinuationTask(Action continuation) =>
(continuation?.Target as ContinuationWrapper)?._innerTask;
/// <summary>
/// Logically we pass just an Action (delegate) to a task for its action to 'ContinueWith' when it completes.
/// However debuggers and profilers need more information about what that action is. (In particular what
/// the action after that is and after that. To solve this problem we create a 'ContinuationWrapper
/// which when invoked just does the original action (the invoke action), but also remembers other information
/// (like the action after that (which is also a ContinuationWrapper and thus form a linked list).
/// We also store that task if the action is associate with at task.
/// </summary>
private sealed class ContinuationWrapper
{
private readonly Action<Action, Task> _invokeAction; // This wrapper is an action that wraps another action, this is that Action.
internal readonly Action _continuation; // This is continuation which will happen after m_invokeAction (and is probably a ContinuationWrapper)
internal readonly Task _innerTask; // If the continuation is logically going to invoke a task, this is that task (may be null)
internal ContinuationWrapper(Action continuation, Action<Action, Task> invokeAction, Task innerTask)
{
Debug.Assert(continuation != null, "Expected non-null continuation");
Debug.Assert(invokeAction != null, "Expected non-null continuation");
_invokeAction = invokeAction;
_continuation = continuation;
_innerTask = innerTask ?? TryGetContinuationTask(continuation); // if we don't have a task, see if our continuation is a wrapper and use that.
}
internal void Invoke() => _invokeAction(_continuation, _innerTask);
}
}
}
|