This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/
DataflowBlock.cs
2765 lines (2491 loc) · 167 KB
/
DataflowBlock.cs
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
// 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.
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// DataflowBlock.cs
//
//
// Common functionality for ITargetBlock, ISourceBlock, and IPropagatorBlock.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading.Tasks.Dataflow.Internal;
#if USE_INTERNAL_THREADING
using System.Threading.Tasks.Dataflow.Internal.Threading;
#endif
namespace System.Threading.Tasks.Dataflow
{
/// <summary>
/// Provides a set of static (Shared in Visual Basic) methods for working with dataflow blocks.
/// </summary>
public static class DataflowBlock
{
#region LinkTo
/// <summary>Links the <see cref="ISourceBlock{TOutput}"/> to the specified <see cref="ITargetBlock{TOutput}"/>.</summary>
/// <param name="source">The source from which to link.</param>
/// <param name="target">The <see cref="ITargetBlock{TOutput}"/> to which to connect the source.</param>
/// <returns>An IDisposable that, upon calling Dispose, will unlink the source from the target.</returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
public static IDisposable LinkTo<TOutput>(
this ISourceBlock<TOutput> source,
ITargetBlock<TOutput> target)
{
// Validate arguments
if (source == null) throw new ArgumentNullException(nameof(source));
if (target == null) throw new ArgumentNullException(nameof(target));
// This method exists purely to pass default DataflowLinkOptions
// to increase usability of the "90%" case.
return source.LinkTo(target, DataflowLinkOptions.Default);
}
/// <summary>Links the <see cref="ISourceBlock{TOutput}"/> to the specified <see cref="ITargetBlock{TOutput}"/> using the specified filter.</summary>
/// <param name="source">The source from which to link.</param>
/// <param name="target">The <see cref="ITargetBlock{TOutput}"/> to which to connect the source.</param>
/// <param name="predicate">The filter a message must pass in order for it to propagate from the source to the target.</param>
/// <returns>An IDisposable that, upon calling Dispose, will unlink the source from the target.</returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentNullException">The <paramref name="predicate"/> is null (Nothing in Visual Basic).</exception>
public static IDisposable LinkTo<TOutput>(
this ISourceBlock<TOutput> source,
ITargetBlock<TOutput> target,
Predicate<TOutput> predicate)
{
// All argument validation handled by delegated method.
return LinkTo(source, target, DataflowLinkOptions.Default, predicate);
}
/// <summary>Links the <see cref="ISourceBlock{TOutput}"/> to the specified <see cref="ITargetBlock{TOutput}"/> using the specified filter.</summary>
/// <param name="source">The source from which to link.</param>
/// <param name="target">The <see cref="ITargetBlock{TOutput}"/> to which to connect the source.</param>
/// <param name="predicate">The filter a message must pass in order for it to propagate from the source to the target.</param>
/// <param name="linkOptions">The options to use to configure the link.</param>
/// <returns>An IDisposable that, upon calling Dispose, will unlink the source from the target.</returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentNullException">The <paramref name="linkOptions"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentNullException">The <paramref name="predicate"/> is null (Nothing in Visual Basic).</exception>
public static IDisposable LinkTo<TOutput>(
this ISourceBlock<TOutput> source,
ITargetBlock<TOutput> target,
DataflowLinkOptions linkOptions,
Predicate<TOutput> predicate)
{
// Validate arguments
if (source == null) throw new ArgumentNullException(nameof(source));
if (target == null) throw new ArgumentNullException(nameof(target));
if (linkOptions == null) throw new ArgumentNullException(nameof(linkOptions));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
// Create the filter, which links to the real target, and then
// link the real source to this intermediate filter.
var filter = new FilteredLinkPropagator<TOutput>(source, target, predicate);
return source.LinkTo(filter, linkOptions);
}
/// <summary>Provides a synchronous filter for use in filtered LinkTos.</summary>
/// <typeparam name="T">Specifies the type of data being filtered.</typeparam>
[DebuggerDisplay("{DebuggerDisplayContent,nq}")]
[DebuggerTypeProxy(typeof(FilteredLinkPropagator<>.DebugView))]
private sealed class FilteredLinkPropagator<T> : IPropagatorBlock<T, T>, IDebuggerDisplay
{
/// <summary>The source connected with this filter.</summary>
private readonly ISourceBlock<T> _source;
/// <summary>The target with which this block is associated.</summary>
private readonly ITargetBlock<T> _target;
/// <summary>The predicate provided by the user.</summary>
private readonly Predicate<T> _userProvidedPredicate;
/// <summary>Initializes the filter passthrough.</summary>
/// <param name="source">The source connected to this filter.</param>
/// <param name="target">The target to which filtered messages should be passed.</param>
/// <param name="predicate">The predicate to run for each message.</param>
internal FilteredLinkPropagator(ISourceBlock<T> source, ITargetBlock<T> target, Predicate<T> predicate)
{
Debug.Assert(source != null, "Filtered link requires a source to filter on.");
Debug.Assert(target != null, "Filtered link requires a target to filter to.");
Debug.Assert(predicate != null, "Filtered link requires a predicate to filter with.");
// Store the arguments
_source = source;
_target = target;
_userProvidedPredicate = predicate;
}
/// <summary>Runs the user-provided predicate over an item in the correct execution context.</summary>
/// <param name="item">The item to evaluate.</param>
/// <returns>true if the item passed the filter; otherwise, false.</returns>
private bool RunPredicate(T item)
{
Debug.Assert(_userProvidedPredicate != null, "User-provided predicate is required.");
return _userProvidedPredicate(item);
}
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Targets/Member[@name="OfferMessage"]/*' />
DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader, T messageValue, ISourceBlock<T> source, bool consumeToAccept)
{
// Validate arguments. Some targets may have a null source, but FilteredLinkPropagator
// is an internal target that should only ever have source non-null.
if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, nameof(messageHeader));
if (source == null) throw new ArgumentNullException(nameof(source));
// Run the filter.
bool passedFilter = RunPredicate(messageValue);
// If the predicate matched, pass the message along to the real target.
if (passedFilter)
{
return _target.OfferMessage(messageHeader, messageValue, this, consumeToAccept);
}
// Otherwise, decline.
else return DataflowMessageStatus.Declined;
}
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ConsumeMessage"]/*' />
T ISourceBlock<T>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target, out bool messageConsumed)
{
// This message should have only made it to the target if it passes the filter, so we shouldn't need to check again.
// The real source will also be doing verifications, so we don't need to validate args here.
Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed.");
return _source.ConsumeMessage(messageHeader, this, out messageConsumed);
}
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReserveMessage"]/*' />
bool ISourceBlock<T>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
{
// This message should have only made it to the target if it passes the filter, so we shouldn't need to check again.
// The real source will also be doing verifications, so we don't need to validate args here.
Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed.");
return _source.ReserveMessage(messageHeader, this);
}
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="ReleaseReservation"]/*' />
void ISourceBlock<T>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<T> target)
{
// This message should have only made it to the target if it passes the filter, so we shouldn't need to check again.
// The real source will also be doing verifications, so we don't need to validate args here.
Debug.Assert(messageHeader.IsValid, "Only valid messages may be consumed.");
_source.ReleaseReservation(messageHeader, this);
}
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
Task IDataflowBlock.Completion { get { return _source.Completion; } }
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
void IDataflowBlock.Complete() { _target.Complete(); }
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
void IDataflowBlock.Fault(Exception exception) { _target.Fault(exception); }
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
IDisposable ISourceBlock<T>.LinkTo(ITargetBlock<T> target, DataflowLinkOptions linkOptions) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
/// <summary>The data to display in the debugger display attribute.</summary>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
private object DebuggerDisplayContent
{
get
{
var displaySource = _source as IDebuggerDisplay;
var displayTarget = _target as IDebuggerDisplay;
return string.Format("{0} Source=\"{1}\", Target=\"{2}\"",
Common.GetNameForDebugger(this),
displaySource != null ? displaySource.Content : _source,
displayTarget != null ? displayTarget.Content : _target);
}
}
/// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
/// <summary>Provides a debugger type proxy for a filter.</summary>
private sealed class DebugView
{
/// <summary>The filter.</summary>
private readonly FilteredLinkPropagator<T> _filter;
/// <summary>Initializes the debug view.</summary>
/// <param name="filter">The filter to view.</param>
public DebugView(FilteredLinkPropagator<T> filter)
{
Debug.Assert(filter != null, "Need a filter with which to construct the debug view.");
_filter = filter;
}
/// <summary>The linked target for this filter.</summary>
public ITargetBlock<T> LinkedTarget { get { return _filter._target; } }
}
}
#endregion
#region Post and SendAsync
/// <summary>Posts an item to the <see cref="T:System.Threading.Tasks.Dataflow.ITargetBlock`1"/>.</summary>
/// <typeparam name="TInput">Specifies the type of data accepted by the target block.</typeparam>
/// <param name="target">The target block.</param>
/// <param name="item">The item being offered to the target.</param>
/// <returns>true if the item was accepted by the target block; otherwise, false.</returns>
/// <remarks>
/// This method will return once the target block has decided to accept or decline the item,
/// but unless otherwise dictated by special semantics of the target block, it does not wait
/// for the item to actually be processed (for example, <see cref="T:System.Threading.Tasks.Dataflow.ActionBlock`1"/>
/// will return from Post as soon as it has stored the posted item into its input queue). From the perspective
/// of the block's processing, Post is asynchronous. For target blocks that support postponing offered messages,
/// or for blocks that may do more processing in their Post implementation, consider using
/// <see cref="T:System.Threading.Tasks.Dataflow.DataflowBlock.SendAsync">SendAsync</see>,
/// which will return immediately and will enable the target to postpone the posted message and later consume it
/// after SendAsync returns.
/// </remarks>
public static bool Post<TInput>(this ITargetBlock<TInput> target, TInput item)
{
if (target == null) throw new ArgumentNullException(nameof(target));
return target.OfferMessage(Common.SingleMessageHeader, item, source: null, consumeToAccept: false) == DataflowMessageStatus.Accepted;
}
/// <summary>Asynchronously offers a message to the target message block, allowing for postponement.</summary>
/// <typeparam name="TInput">Specifies the type of the data to post to the target.</typeparam>
/// <param name="target">The target to which to post the data.</param>
/// <param name="item">The item being offered to the target.</param>
/// <returns>
/// A <see cref="System.Threading.Tasks.Task{Boolean}"/> that represents the asynchronous send. If the target
/// accepts and consumes the offered element during the call to SendAsync, upon return
/// from the call the resulting <see cref="System.Threading.Tasks.Task{Boolean}"/> will be completed and its <see cref="System.Threading.Tasks.Task{Boolean}.Result">Result</see>
/// property will return true. If the target declines the offered element during the call, upon return from the call the resulting <see cref="System.Threading.Tasks.Task{Boolean}"/> will
/// be completed and its <see cref="System.Threading.Tasks.Task{Boolean}.Result">Result</see> property will return false. If the target
/// postpones the offered element, the element will be buffered until such time that the target consumes or releases it, at which
/// point the Task will complete, with its <see cref="System.Threading.Tasks.Task{Boolean}.Result"/> indicating whether the message was consumed. If the target
/// never attempts to consume or release the message, the returned task will never complete.
/// </returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
public static Task<bool> SendAsync<TInput>(this ITargetBlock<TInput> target, TInput item)
{
return SendAsync<TInput>(target, item, CancellationToken.None);
}
/// <summary>Asynchronously offers a message to the target message block, allowing for postponement.</summary>
/// <typeparam name="TInput">Specifies the type of the data to post to the target.</typeparam>
/// <param name="target">The target to which to post the data.</param>
/// <param name="item">The item being offered to the target.</param>
/// <param name="cancellationToken">The cancellation token with which to request cancellation of the send operation.</param>
/// <returns>
/// <para>
/// A <see cref="System.Threading.Tasks.Task{Boolean}"/> that represents the asynchronous send. If the target
/// accepts and consumes the offered element during the call to SendAsync, upon return
/// from the call the resulting <see cref="System.Threading.Tasks.Task{Boolean}"/> will be completed and its <see cref="System.Threading.Tasks.Task{Boolean}.Result">Result</see>
/// property will return true. If the target declines the offered element during the call, upon return from the call the resulting <see cref="System.Threading.Tasks.Task{Boolean}"/> will
/// be completed and its <see cref="System.Threading.Tasks.Task{Boolean}.Result">Result</see> property will return false. If the target
/// postpones the offered element, the element will be buffered until such time that the target consumes or releases it, at which
/// point the Task will complete, with its <see cref="System.Threading.Tasks.Task{Boolean}.Result"/> indicating whether the message was consumed. If the target
/// never attempts to consume or release the message, the returned task will never complete.
/// </para>
/// <para>
/// If cancellation is requested before the target has successfully consumed the sent data,
/// the returned task will complete in the Canceled state and the data will no longer be available to the target.
/// </para>
/// </returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="target"/> is null (Nothing in Visual Basic).</exception>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public static Task<bool> SendAsync<TInput>(this ITargetBlock<TInput> target, TInput item, CancellationToken cancellationToken)
{
// Validate arguments. No validation necessary for item.
if (target == null) throw new ArgumentNullException(nameof(target));
// Fast path check for cancellation
if (cancellationToken.IsCancellationRequested)
return Common.CreateTaskFromCancellation<Boolean>(cancellationToken);
SendAsyncSource<TInput> source;
// Fast path: try to offer the item synchronously. This first try is done
// without any form of cancellation, and thus consumeToAccept can be the better-performing "false".
try
{
switch (target.OfferMessage(Common.SingleMessageHeader, item, source: null, consumeToAccept: false))
{
// If the message is immediately accepted, return a cached completed task with a true result
case DataflowMessageStatus.Accepted:
return Common.CompletedTaskWithTrueResult;
// If the target is declining permanently, return a cached completed task with a false result
case DataflowMessageStatus.DecliningPermanently:
return Common.CompletedTaskWithFalseResult;
#if DEBUG
case DataflowMessageStatus.Postponed:
Debug.Assert(false, "A message should never be postponed when no source has been provided");
break;
case DataflowMessageStatus.NotAvailable:
Debug.Assert(false, "The message should never be missed, as it's offered to only this one target");
break;
#endif
}
// Slow path: the target did not accept the synchronous post, nor did it decline it.
// Create a source for the send, launch the offering, and return the representative task.
// This ctor attempts to register a cancellation notification which would throw if the
// underlying CTS has been disposed of. Therefore, keep it inside the try/catch block.
source = new SendAsyncSource<TInput>(target, item, cancellationToken);
}
catch (Exception exc)
{
// If the target throws from OfferMessage, return a faulted task
Common.StoreDataflowMessageValueIntoExceptionData(exc, item);
return Common.CreateTaskFromException<Boolean>(exc);
}
Debug.Assert(source != null, "The SendAsyncSource instance must have been constructed.");
source.OfferToTarget(); // synchronous to preserve message ordering
return source.Task;
}
/// <summary>
/// Provides a source used by SendAsync that will buffer a single message and signal when it's been accepted or declined.
/// </summary>
/// <remarks>This source must only be passed to a single target, and must only be used once.</remarks>
[DebuggerDisplay("{DebuggerDisplayContent,nq}")]
[DebuggerTypeProxy(typeof(SendAsyncSource<>.DebugView))]
private sealed class SendAsyncSource<TOutput> : TaskCompletionSource<bool>, ISourceBlock<TOutput>, IDebuggerDisplay
{
/// <summary>The target to offer to.</summary>
private readonly ITargetBlock<TOutput> _target;
/// <summary>The buffered message.</summary>
private readonly TOutput _messageValue;
/// <summary>CancellationToken used to cancel the send.</summary>
private CancellationToken _cancellationToken;
/// <summary>Registration with the cancellation token.</summary>
private CancellationTokenRegistration _cancellationRegistration;
/// <summary>The cancellation/completion state of the source.</summary>
private int _cancellationState; // one of the CANCELLATION_STATE_* constant values, defaulting to NONE
// Cancellation states:
// _cancellationState starts out as NONE, and will remain that way unless a CancellationToken
// is provided in the initial OfferToTarget call. As such, unless a token is provided,
// all synchronization related to cancellation will be avoided. Once a token is provided,
// the state transitions to REGISTERED. If cancellation then is requested or if the target
// calls back to consume the message, the state will transition to COMPLETING prior to
// actually committing the action; if it can't transition to COMPLETING, then the action doesn't
// take effect (e.g. if cancellation raced with the target consuming, such that the cancellation
// action was able to transition to COMPLETING but the consumption wasn't, then ConsumeMessage
// would return false indicating that the message could not be consumed). The only additional
// complication here is around reservations. If a target reserves a message, _cancellationState
// transitions to RESERVED. A subsequent ConsumeMessage call can successfully transition from
// RESERVED to COMPLETING, but cancellation can't; cancellation can only transition from REGISTERED
// to COMPLETING. If the reservation on the message is instead released, _cancellationState
// will transition back to REGISTERED.
/// <summary>No cancellation registration is used.</summary>
private const int CANCELLATION_STATE_NONE = 0;
/// <summary>A cancellation token has been registered.</summary>
private const int CANCELLATION_STATE_REGISTERED = 1;
/// <summary>The message has been reserved. Only used if a cancellation token is in play.</summary>
private const int CANCELLATION_STATE_RESERVED = 2;
/// <summary>Completion is now in progress. Only used if a cancellation token is in play.</summary>
private const int CANCELLATION_STATE_COMPLETING = 3;
/// <summary>Initializes the source.</summary>
/// <param name="target">The target to offer to.</param>
/// <param name="messageValue">The message to offer and buffer.</param>
/// <param name="cancellationToken">The cancellation token with which to cancel the send.</param>
internal SendAsyncSource(ITargetBlock<TOutput> target, TOutput messageValue, CancellationToken cancellationToken)
{
Debug.Assert(target != null, "A valid target to send to is required.");
_target = target;
_messageValue = messageValue;
// If a cancelable CancellationToken is used, update our cancellation state
// and register with the token. Only if CanBeCanceled is true due we want
// to pay the subsequent costs around synchronization between cancellation
// requests and the target coming back to consume the message.
if (cancellationToken.CanBeCanceled)
{
_cancellationToken = cancellationToken;
_cancellationState = CANCELLATION_STATE_REGISTERED;
try
{
_cancellationRegistration = cancellationToken.Register(
_cancellationCallback, new WeakReference<SendAsyncSource<TOutput>>(this));
}
catch
{
// Suppress finalization. Finalization is only required if the target drops a reference
// to the source before the source has completed, and we'll never offer to the target.
GC.SuppressFinalize(this);
// Propagate the exception
throw;
}
}
}
/// <summary>Finalizer that completes the returned task if all references to this source are dropped.</summary>
~SendAsyncSource()
{
// CompleteAsDeclined uses synchronization, which is dangerous for a finalizer
// during shutdown or appdomain unload.
if (!Environment.HasShutdownStarted)
{
CompleteAsDeclined(runAsync: true);
}
}
/// <summary>Completes the source in an "Accepted" state.</summary>
/// <param name="runAsync">true to accept asynchronously; false to accept synchronously.</param>
private void CompleteAsAccepted(bool runAsync)
{
RunCompletionAction(state =>
{
try { ((SendAsyncSource<TOutput>)state).TrySetResult(true); }
catch (ObjectDisposedException) { }
}, this, runAsync);
}
/// <summary>Completes the source in an "Declined" state.</summary>
/// <param name="runAsync">true to decline asynchronously; false to decline synchronously.</param>
private void CompleteAsDeclined(bool runAsync)
{
RunCompletionAction(state =>
{
// The try/catch for ObjectDisposedException handles the case where the
// user disposes of the returned task before we're done with it.
try { ((SendAsyncSource<TOutput>)state).TrySetResult(false); }
catch (ObjectDisposedException) { }
}, this, runAsync);
}
/// <summary>Completes the source in faulted state.</summary>
/// <param name="exception">The exception with which to fault.</param>
/// <param name="runAsync">true to fault asynchronously; false to fault synchronously.</param>
private void CompleteAsFaulted(Exception exception, bool runAsync)
{
RunCompletionAction(state =>
{
var tuple = (Tuple<SendAsyncSource<TOutput>, Exception>)state;
try { tuple.Item1.TrySetException(tuple.Item2); }
catch (ObjectDisposedException) { }
}, Tuple.Create<SendAsyncSource<TOutput>, Exception>(this, exception), runAsync);
}
/// <summary>Completes the source in canceled state.</summary>
/// <param name="runAsync">true to fault asynchronously; false to fault synchronously.</param>
private void CompleteAsCanceled(bool runAsync)
{
RunCompletionAction(state =>
{
try { ((SendAsyncSource<TOutput>)state).TrySetCanceled(); }
catch (ObjectDisposedException) { }
}, this, runAsync);
}
/// <summary>Executes a completion action.</summary>
/// <param name="completionAction">The action to execute, passed the state.</param>
/// <param name="completionActionState">The state to pass into the delegate.</param>
/// <param name="runAsync">true to execute the action asynchronously; false to execute it synchronously.</param>
/// <remarks>
/// async should be true if this is being called on a path that has the target on the stack, e.g.
/// the target is calling to ConsumeMessage. We don't want to block the target indefinitely
/// with any synchronous continuations off of the returned send async task.
/// </remarks>
[SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly")]
private void RunCompletionAction(Action<object> completionAction, object completionActionState, bool runAsync)
{
Debug.Assert(completionAction != null, "Completion action to run is required.");
// Suppress finalization. Finalization is only required if the target drops a reference
// to the source before the source has completed, and here we're completing the source.
GC.SuppressFinalize(this);
// Dispose of the cancellation registration if there is one
if (_cancellationState != CANCELLATION_STATE_NONE)
{
Debug.Assert(_cancellationRegistration != default(CancellationTokenRegistration),
"If we're not in NONE, we must have a cancellation token we've registered with.");
_cancellationRegistration.Dispose();
}
// If we're meant to run asynchronously, launch a task.
if (runAsync)
{
System.Threading.Tasks.Task.Factory.StartNew(
completionAction, completionActionState,
CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
}
// Otherwise, execute directly.
else
{
completionAction(completionActionState);
}
}
/// <summary>Offers the message to the target asynchronously.</summary>
private void OfferToTargetAsync()
{
System.Threading.Tasks.Task.Factory.StartNew(
state => ((SendAsyncSource<TOutput>)state).OfferToTarget(), this,
CancellationToken.None, Common.GetCreationOptionsForTask(), TaskScheduler.Default);
}
/// <summary>Cached delegate used to cancel a send in response to a cancellation request.</summary>
private static readonly Action<object> _cancellationCallback = CancellationHandler;
/// <summary>Attempts to cancel the source passed as state in response to a cancellation request.</summary>
/// <param name="state">
/// A weak reference to the SendAsyncSource. A weak reference is used to prevent the source
/// from being rooted in a long-lived token.
/// </param>
private static void CancellationHandler(object state)
{
SendAsyncSource<TOutput> source = Common.UnwrapWeakReference<SendAsyncSource<TOutput>>(state);
if (source != null)
{
Debug.Assert(source._cancellationState != CANCELLATION_STATE_NONE,
"If cancellation is in play, we must have already moved out of the NONE state.");
// Try to reserve completion, and if we can, complete as canceled. Note that we can only
// achieve cancellation when in the REGISTERED state, and not when in the RESERVED state,
// as if a target has reserved the message, we must allow the message to be consumed successfully.
if (source._cancellationState == CANCELLATION_STATE_REGISTERED && // fast check to avoid the interlocked if we can
Interlocked.CompareExchange(ref source._cancellationState, CANCELLATION_STATE_COMPLETING, CANCELLATION_STATE_REGISTERED) == CANCELLATION_STATE_REGISTERED)
{
// We've reserved completion, so proceed to cancel the task.
source.CompleteAsCanceled(true);
}
}
}
/// <summary>Offers the message to the target synchronously.</summary>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
internal void OfferToTarget()
{
try
{
// Offer the message to the target. If there's no cancellation in play, we can just allow the target
// to accept the message directly. But if a CancellationToken is in use, the target needs to come
// back to us to get the data; that way, we can ensure we don't race between returning a canceled task but
// successfully completing the send.
bool consumeToAccept = _cancellationState != CANCELLATION_STATE_NONE;
switch (_target.OfferMessage(
Common.SingleMessageHeader, _messageValue, this, consumeToAccept: consumeToAccept))
{
// If the message is immediately accepted, complete the task as accepted
case DataflowMessageStatus.Accepted:
if (!consumeToAccept)
{
// Cancellation wasn't in use, and the target accepted the message directly,
// so complete the task as accepted.
CompleteAsAccepted(runAsync: false);
}
else
{
// If cancellation is in use, then since the target accepted,
// our state better reflect that we're completing.
Debug.Assert(_cancellationState == CANCELLATION_STATE_COMPLETING,
"The message was accepted, so we should have started completion.");
}
break;
// If the message is immediately declined, complete the task as declined
case DataflowMessageStatus.Declined:
case DataflowMessageStatus.DecliningPermanently:
CompleteAsDeclined(runAsync: false);
break;
#if DEBUG
case DataflowMessageStatus.NotAvailable:
Debug.Assert(false, "The message should never be missed, as it's offered to only this one target");
break;
// If the message was postponed, the source may or may not be complete yet. Nothing to validate.
// Treat an improper DataflowMessageStatus as postponed and do nothing.
#endif
}
}
// A faulty target might throw from OfferMessage. If that happens,
// we'll try to fault the returned task. A really faulty target might
// both throw from OfferMessage and call ConsumeMessage,
// in which case it's possible we might not be able to propagate the exception
// out to the caller through the task if ConsumeMessage wins the race,
// which is likely if the exception doesn't occur until after ConsumeMessage is
// called. If that happens, we just eat the exception.
catch (Exception exc)
{
Common.StoreDataflowMessageValueIntoExceptionData(exc, _messageValue);
CompleteAsFaulted(exc, runAsync: false);
}
}
/// <summary>Called by the target to consume the buffered message.</summary>
TOutput ISourceBlock<TOutput>.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target, out bool messageConsumed)
{
// Validate arguments
if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, nameof(messageHeader));
if (target == null) throw new ArgumentNullException(nameof(target));
// If the task has already completed, there's nothing to consume. This could happen if
// cancellation was already requested and completed the task as a result.
if (Task.IsCompleted)
{
messageConsumed = false;
return default(TOutput);
}
// If the message being asked for is not the same as the one that's buffered,
// something is wrong. Complete as having failed to transfer the message.
bool validMessage = (messageHeader.Id == Common.SINGLE_MESSAGE_ID);
if (validMessage)
{
int curState = _cancellationState;
Debug.Assert(
curState == CANCELLATION_STATE_NONE || curState == CANCELLATION_STATE_REGISTERED ||
curState == CANCELLATION_STATE_RESERVED || curState == CANCELLATION_STATE_COMPLETING,
"The current cancellation state is not valid.");
// If we're not dealing with cancellation, then if we're currently registered or reserved, try to transition
// to completing. If we're able to, allow the message to be consumed, and we're done. At this point, we
// support transitioning out of REGISTERED or RESERVED.
if (curState == CANCELLATION_STATE_NONE || // no synchronization necessary if there's no cancellation
(curState != CANCELLATION_STATE_COMPLETING && // fast check to avoid unnecessary synchronization
Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_COMPLETING, curState) == curState))
{
CompleteAsAccepted(runAsync: true);
messageConsumed = true;
return _messageValue;
}
}
// Consumption failed
messageConsumed = false;
return default(TOutput);
}
/// <summary>Called by the target to reserve the buffered message.</summary>
bool ISourceBlock<TOutput>.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
{
// Validate arguments
if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, nameof(messageHeader));
if (target == null) throw new ArgumentNullException(nameof(target));
// If the task has already completed, such as due to cancellation, there's nothing to reserve.
if (Task.IsCompleted) return false;
// As long as the message is the one being requested and cancellation hasn't been requested, allow it to be reserved.
bool reservable = (messageHeader.Id == Common.SINGLE_MESSAGE_ID);
return reservable &&
(_cancellationState == CANCELLATION_STATE_NONE || // avoid synchronization when cancellation is not in play
Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_RESERVED, CANCELLATION_STATE_REGISTERED) == CANCELLATION_STATE_REGISTERED);
}
/// <summary>Called by the target to release a reservation on the buffered message.</summary>
void ISourceBlock<TOutput>.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock<TOutput> target)
{
// Validate arguments
if (!messageHeader.IsValid) throw new ArgumentException(SR.Argument_InvalidMessageHeader, nameof(messageHeader));
if (target == null) throw new ArgumentNullException(nameof(target));
// If this is not the message we posted, bail
if (messageHeader.Id != Common.SINGLE_MESSAGE_ID)
throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
// If the task has already completed, there's nothing to release.
if (Task.IsCompleted) return;
// If a cancellation token is being used, revert our state back to registered. In the meantime
// cancellation could have been requested, so check to see now if cancellation was requested
// and process it if it was.
if (_cancellationState != CANCELLATION_STATE_NONE)
{
if (Interlocked.CompareExchange(ref _cancellationState, CANCELLATION_STATE_REGISTERED, CANCELLATION_STATE_RESERVED) != CANCELLATION_STATE_RESERVED)
throw new InvalidOperationException(SR.InvalidOperation_MessageNotReservedByTarget);
if (_cancellationToken.IsCancellationRequested)
CancellationHandler(new WeakReference<SendAsyncSource<TOutput>>(this)); // same code as registered with the CancellationToken
}
// Start the process over by reoffering the message asynchronously.
OfferToTargetAsync();
}
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Completion"]/*' />
Task IDataflowBlock.Completion { get { return Task; } }
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Sources/Member[@name="LinkTo"]/*' />
IDisposable ISourceBlock<TOutput>.LinkTo(ITargetBlock<TOutput> target, DataflowLinkOptions linkOptions) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Complete"]/*' />
void IDataflowBlock.Complete() { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
/// <include file='XmlDocs/CommonXmlDocComments.xml' path='CommonXmlDocComments/Blocks/Member[@name="Fault"]/*' />
void IDataflowBlock.Fault(Exception exception) { throw new NotSupportedException(SR.NotSupported_MemberNotNeeded); }
/// <summary>The data to display in the debugger display attribute.</summary>
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider")]
private object DebuggerDisplayContent
{
get
{
var displayTarget = _target as IDebuggerDisplay;
return string.Format("{0} Message={1}, Target=\"{2}\"",
Common.GetNameForDebugger(this),
_messageValue,
displayTarget != null ? displayTarget.Content : _target);
}
}
/// <summary>Gets the data to display in the debugger display attribute for this instance.</summary>
object IDebuggerDisplay.Content { get { return DebuggerDisplayContent; } }
/// <summary>Provides a debugger type proxy for the source.</summary>
private sealed class DebugView
{
/// <summary>The source.</summary>
private readonly SendAsyncSource<TOutput> _source;
/// <summary>Initializes the debug view.</summary>
/// <param name="source">The source to view.</param>
public DebugView(SendAsyncSource<TOutput> source)
{
Debug.Assert(source != null, "Need a source with which to construct the debug view.");
_source = source;
}
/// <summary>The target to which we're linked.</summary>
public ITargetBlock<TOutput> Target { get { return _source._target; } }
/// <summary>The message buffered by the source.</summary>
public TOutput Message { get { return _source._messageValue; } }
/// <summary>The Task represented the posting of the message.</summary>
public Task<bool> Completion { get { return _source.Task; } }
}
}
#endregion
#region TryReceive, ReceiveAsync, and Receive
#region TryReceive
/// <summary>
/// Attempts to synchronously receive an item from the <see cref="T:System.Threading.Tasks.Dataflow.ISourceBlock`1"/>.
/// </summary>
/// <param name="source">The source from which to receive.</param>
/// <param name="item">The item received from the source.</param>
/// <returns>true if an item could be received; otherwise, false.</returns>
/// <remarks>
/// This method does not wait until the source has an item to provide.
/// It will return whether or not an element was available.
/// </remarks>
public static bool TryReceive<TOutput>(this IReceivableSourceBlock<TOutput> source, out TOutput item)
{
if (source == null) throw new ArgumentNullException(nameof(source));
return source.TryReceive(null, out item);
}
#endregion
#region ReceiveAsync
/// <summary>Asynchronously receives a value from the specified source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to asynchronously receive.</param>
/// <returns>
/// A <see cref="System.Threading.Tasks.Task{TOutput}"/> that represents the asynchronous receive operation. When an item is successfully received from the source,
/// the returned task will be completed and its <see cref="System.Threading.Tasks.Task{TOutput}.Result">Result</see> will return the received item. If an item cannot be retrieved,
/// because the source is empty and completed, the returned task will be canceled.
/// </returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
public static Task<TOutput> ReceiveAsync<TOutput>(
this ISourceBlock<TOutput> source)
{
// Argument validation handled by target method
return ReceiveAsync(source, Common.InfiniteTimeSpan, CancellationToken.None);
}
/// <summary>Asynchronously receives a value from the specified source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to asynchronously receive.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
/// <returns>
/// A <see cref="System.Threading.Tasks.Task{TOutput}"/> that represents the asynchronous receive operation. When an item is successfully received from the source,
/// the returned task will be completed and its <see cref="System.Threading.Tasks.Task{TOutput}.Result">Result</see> will return the received item. If an item cannot be retrieved,
/// either because cancellation is requested or the source is empty and completed, the returned task will be canceled.
/// </returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
public static Task<TOutput> ReceiveAsync<TOutput>(
this ISourceBlock<TOutput> source, CancellationToken cancellationToken)
{
// Argument validation handled by target method
return ReceiveAsync(source, Common.InfiniteTimeSpan, cancellationToken);
}
/// <summary>Asynchronously receives a value from the specified source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to asynchronously receive.</param>
/// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
/// <returns>
/// A <see cref="System.Threading.Tasks.Task{TOutput}"/> that represents the asynchronous receive operation. When an item is successfully received from the source,
/// the returned task will be completed and its <see cref="System.Threading.Tasks.Task{TOutput}.Result">Result</see> will return the received item. If an item cannot be retrieved,
/// either because the timeout expires or the source is empty and completed, the returned task will be canceled.
/// </returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
/// </exception>
public static Task<TOutput> ReceiveAsync<TOutput>(
this ISourceBlock<TOutput> source, TimeSpan timeout)
{
// Argument validation handled by target method
return ReceiveAsync(source, timeout, CancellationToken.None);
}
/// <summary>Asynchronously receives a value from the specified source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to asynchronously receive.</param>
/// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
/// <returns>
/// A <see cref="System.Threading.Tasks.Task{TOutput}"/> that represents the asynchronous receive operation. When an item is successfully received from the source,
/// the returned task will be completed and its <see cref="System.Threading.Tasks.Task{TOutput}.Result">Result</see> will return the received item. If an item cannot be retrieved,
/// either because the timeout expires, cancellation is requested, or the source is empty and completed, the returned task will be canceled.
/// </returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
/// </exception>
public static Task<TOutput> ReceiveAsync<TOutput>(
this ISourceBlock<TOutput> source, TimeSpan timeout, CancellationToken cancellationToken)
{
// Validate arguments
if (source == null) throw new ArgumentNullException(nameof(source));
if (!Common.IsValidTimeout(timeout)) throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
// Return the task representing the core receive operation
return ReceiveCore(source, true, timeout, cancellationToken);
}
#endregion
#region Receive
/// <summary>Synchronously receives an item from the source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to receive.</param>
/// <returns>The received item.</returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">No item could be received from the source.</exception>
public static TOutput Receive<TOutput>(
this ISourceBlock<TOutput> source)
{
// Argument validation handled by target method
return Receive(source, Common.InfiniteTimeSpan, CancellationToken.None);
}
/// <summary>Synchronously receives an item from the source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to receive.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
/// <returns>The received item.</returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">No item could be received from the source.</exception>
/// <exception cref="System.OperationCanceledException">The operation was canceled before an item was received from the source.</exception>
/// <remarks>
/// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent cancellation request occurs.
/// </remarks>
public static TOutput Receive<TOutput>(
this ISourceBlock<TOutput> source, CancellationToken cancellationToken)
{
// Argument validation handled by target method
return Receive(source, Common.InfiniteTimeSpan, cancellationToken);
}
/// <summary>Synchronously receives an item from the source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to receive.</param>
/// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
/// <returns>The received item.</returns>
/// <exception cref="System.ArgumentOutOfRangeException">
/// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
/// </exception>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">No item could be received from the source.</exception>
/// <exception cref="System.TimeoutException">The specified timeout expired before an item was received from the source.</exception>
/// <remarks>
/// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent timeout occurs.
/// </remarks>
public static TOutput Receive<TOutput>(
this ISourceBlock<TOutput> source, TimeSpan timeout)
{
// Argument validation handled by target method
return Receive(source, timeout, CancellationToken.None);
}
/// <summary>Synchronously receives an item from the source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to receive.</param>
/// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
/// <returns>The received item.</returns>
/// <exception cref="System.ArgumentNullException">The <paramref name="source"/> is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// timeout is a negative number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>.
/// </exception>
/// <exception cref="System.InvalidOperationException">No item could be received from the source.</exception>
/// <exception cref="System.TimeoutException">The specified timeout expired before an item was received from the source.</exception>
/// <exception cref="System.OperationCanceledException">The operation was canceled before an item was received from the source.</exception>
/// <remarks>
/// If the source successfully offered an item that was received by this operation, it will be returned, even if a concurrent timeout or cancellation request occurs.
/// </remarks>
[SuppressMessage("Microsoft.Usage", "CA2200:RethrowToPreserveStackDetails")]
public static TOutput Receive<TOutput>(
this ISourceBlock<TOutput> source, TimeSpan timeout, CancellationToken cancellationToken)
{
// Validate arguments
if (source == null) throw new ArgumentNullException(nameof(source));
if (!Common.IsValidTimeout(timeout)) throw new ArgumentOutOfRangeException(nameof(timeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
// Do fast path checks for both cancellation and data already existing.
cancellationToken.ThrowIfCancellationRequested();
TOutput fastCheckedItem;
var receivableSource = source as IReceivableSourceBlock<TOutput>;
if (receivableSource != null && receivableSource.TryReceive(null, out fastCheckedItem))
{
return fastCheckedItem;
}
// Get a TCS to represent the receive operation and wait for it to complete.
// If it completes successfully, return the result. Otherwise, throw the
// original inner exception representing the cause. This could be an OCE.
Task<TOutput> task = ReceiveCore(source, false, timeout, cancellationToken);
try
{
return task.GetAwaiter().GetResult(); // block until the result is available
}
catch
{
// Special case cancellation in order to ensure the exception contains the token.
// The public TrySetCanceled, used by ReceiveCore, is parameterless and doesn't
// accept the token to use. Thus the exception that we're catching here
// won't contain the cancellation token we want propagated.
if (task.IsCanceled) cancellationToken.ThrowIfCancellationRequested();
// If we get here, propagate the original exception.
throw;
}
}
#endregion
#region Shared by Receive and ReceiveAsync
/// <summary>Receives an item from the source.</summary>
/// <typeparam name="TOutput">Specifies the type of data contained in the source.</typeparam>
/// <param name="source">The source from which to receive.</param>
/// <param name="attemptTryReceive">Whether to first attempt using TryReceive to get a value from the source.</param>
/// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param>
/// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken"/> which may be used to cancel the receive operation.</param>
/// <returns>A Task for the receive operation.</returns>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
private static Task<TOutput> ReceiveCore<TOutput>(
this ISourceBlock<TOutput> source, bool attemptTryReceive, TimeSpan timeout, CancellationToken cancellationToken)
{
Debug.Assert(source != null, "Need a source from which to receive.");
// If cancellation has been requested, we're done before we've even started, cancel this receive.
if (cancellationToken.IsCancellationRequested)
{
return Common.CreateTaskFromCancellation<TOutput>(cancellationToken);
}
if (attemptTryReceive)
{
// If we're able to directly and immediately receive an item, use that item to complete the receive.
var receivableSource = source as IReceivableSourceBlock<TOutput>;
if (receivableSource != null)