/// <summary>Initializes the <see cref="WriteOnceBlock{T}"/> with the specified <see cref="DataflowBlockOptions"/>.</summary> /// <param name="cloningFunction"> /// The function to use to clone the data when offered to other blocks. /// This may be null to indicate that no cloning need be performed. /// </param> /// <param name="dataflowBlockOptions">The options with which to configure this <see cref="WriteOnceBlock{T}"/>.</param> /// <exception cref="System.ArgumentNullException">The <paramref name="dataflowBlockOptions"/> is null (Nothing in Visual Basic).</exception> public WriteOnceBlock(Func <T, T> cloningFunction, DataflowBlockOptions dataflowBlockOptions) { // Validate arguments if (dataflowBlockOptions == null) { throw new ArgumentNullException("dataflowBlockOptions"); } Contract.EndContractBlock(); // Store the option _cloningFunction = cloningFunction; _dataflowBlockOptions = dataflowBlockOptions.DefaultOrClone(); // The target registry also serves as our ValueLock, // and thus must always be initialized, even if the block is pre-canceled, as // subsequent usage of the block may run through code paths that try to take this lock. _targetRegistry = new TargetRegistry <T>(this); // If a cancelable CancellationToken has been passed in, // we need to initialize the completion task's TCS now. if (dataflowBlockOptions.CancellationToken.CanBeCanceled) { _lazyCompletionTaskSource = new TaskCompletionSource <VoidResult>(); // If we've already had cancellation requested, do as little work as we have to // in order to be done. if (dataflowBlockOptions.CancellationToken.IsCancellationRequested) { _completionReserved = _decliningPermanently = true; // Cancel the completion task's TCS _lazyCompletionTaskSource.SetCanceled(); } else { // Handle async cancellation requests by declining on the target Common.WireCancellationToComplete( dataflowBlockOptions.CancellationToken, _lazyCompletionTaskSource.Task, state => ((WriteOnceBlock <T>)state).Complete(), this); } } #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCreated(this, dataflowBlockOptions); } #endif }
/// <summary>Completes the block.</summary> /// <remarks> /// This is called only once. /// </remarks> private void CompleteBlock(IList <Exception> exceptions) { // Do not invoke the CompletionTaskSource property if there is a chance that _lazyCompletionTaskSource // has not been initialized yet and we may have to complete normally, because that would defeat the // sole purpose of the TCS being lazily initialized. Contract.Requires(_lazyCompletionTaskSource == null || !_lazyCompletionTaskSource.Task.IsCompleted, "The task completion source must not be completed. This must be the only thread that ever completes the block."); // Save the linked list of targets so that it could be traversed later to propagate completion TargetRegistry <T> .LinkedTargetInfo linkedTargets = _targetRegistry.ClearEntryPoints(); // Complete the block's completion task if (exceptions != null && exceptions.Count > 0) { CompletionTaskSource.TrySetException(exceptions); } else if (_dataflowBlockOptions.CancellationToken.IsCancellationRequested) { CompletionTaskSource.TrySetCanceled(); } else { // Safely try to initialize the completion task's TCS with a cached completed TCS. // If our attempt succeeds (CompareExchange returns null), we have nothing more to do. // If the completion task's TCS was already initialized (CompareExchange returns non-null), // we have to complete that TCS instance. if (Interlocked.CompareExchange(ref _lazyCompletionTaskSource, Common.CompletedVoidResultTaskCompletionSource, null) != null) { _lazyCompletionTaskSource.TrySetResult(default(VoidResult)); } } // Now that the completion task is completed, we may propagate completion to the linked targets _targetRegistry.PropagateCompletion(linkedTargets); #if FEATURE_TRACING DataflowEtwProvider etwLog = DataflowEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.DataflowBlockCompleted(this); } #endif }
private List <Exception> OfferToTargets() { Common.ContractAssertMonitorStatus(ValueLock, held: false); // If there is a message, offer it to everyone. Return values // don't matter, because we only get one message and then complete, // and everyone who wants a copy can get a copy. List <Exception> exceptions = null; if (HasValue) { TargetRegistry <T> .LinkedTargetInfo cur = _targetRegistry.FirstTargetNode; while (cur != null) { TargetRegistry <T> .LinkedTargetInfo next = cur.Next; ITargetBlock <T> target = cur.Target; try { // Offer the message. If there's a cloning function, we force the target to // come back to us to consume the message, allowing us the opportunity to run // the cloning function once we know they want the data. If there is no cloning // function, there's no reason for them to call back here. bool useCloning = _cloningFunction != null; target.OfferMessage(_header, _value, this, consumeToAccept: useCloning); } catch (Exception exc) { // Track any erroneous exceptions that may occur // and return them to the caller so that they may // be logged in the completion task. Common.StoreDataflowMessageValueIntoExceptionData(exc, _value); Common.AddException(ref exceptions, exc); } cur = next; } } return(exceptions); }