Esempio n. 1
0
            public override bool TryComplete(Exception?error)
            {
                BoundedChannel <T> parent = _parent;
                bool completeTask;

                lock (parent.SyncObj)
                {
                    parent.AssertInvariants();

                    // If we've already marked the channel as completed, bail.
                    if (parent._doneWriting != null)
                    {
                        return(false);
                    }

                    // Mark that we're done writing.
                    parent._doneWriting = error ?? ChannelUtilities.s_doneWritingSentinel;
                    completeTask        = parent._items.IsEmpty;
                }

                // If there are no items in the queue, complete the channel's task,
                // as no more data can possibly arrive at this point.  We do this outside
                // of the lock in case we'll be running synchronous completions, and we
                // do it before completing blocked/waiting readers, so that when they
                // wake up they'll see the task as being completed.
                if (completeTask)
                {
                    ChannelUtilities.Complete(parent._completion, error);
                }

                // At this point, _blockedReaders/Writers and _waitingReaders/Writers will not be mutated:
                // they're only mutated by readers/writers while holding the lock, and only if _doneWriting is null.
                // We also know that only one thread (this one) will ever get here, as only that thread
                // will be the one to transition from _doneWriting false to true.  As such, we can
                // freely manipulate them without any concurrency concerns.
                ChannelUtilities.FailOperations <AsyncOperation <T>, T>(parent._blockedReaders, ChannelUtilities.CreateInvalidCompletionException(error));
                ChannelUtilities.FailOperations <VoidAsyncOperationWithData <T>, VoidResult>(parent._blockedWriters, ChannelUtilities.CreateInvalidCompletionException(error));
                ChannelUtilities.WakeUpWaiters(ref parent._waitingReadersTail, result: false, error: error);
                ChannelUtilities.WakeUpWaiters(ref parent._waitingWritersTail, result: false, error: error);

                // Successfully transitioned to completed.
                return(true);
            }
Esempio n. 2
0
            public override bool TryWrite(T item)
            {
                AsyncOperation <T>?   blockedReader      = null;
                AsyncOperation <bool>?waitingReadersTail = null;

                BoundedChannel <T> parent = _parent;

                lock (parent.SyncObj)
                {
                    parent.AssertInvariants();

                    // If we're done writing, nothing more to do.
                    if (parent._doneWriting != null)
                    {
                        return(false);
                    }

                    // Get the number of items in the channel currently.
                    int count = parent._items.Count;

                    if (count == 0)
                    {
                        // There are no items in the channel, which means we may have blocked/waiting readers.

                        // If there are any blocked readers, find one that's not canceled
                        // and store it to complete outside of the lock, in case it has
                        // continuations that'll run synchronously
                        while (!parent._blockedReaders.IsEmpty)
                        {
                            AsyncOperation <T> r = parent._blockedReaders.DequeueHead();
                            r.UnregisterCancellation(); // ensure that once we grab it, we own its completion
                            if (!r.IsCompleted)
                            {
                                blockedReader = r;
                                break;
                            }
                        }

                        if (blockedReader == null)
                        {
                            // If there wasn't a blocked reader, then store the item. If no one's waiting
                            // to be notified about a 0-to-1 transition, we're done.
                            parent._items.EnqueueTail(item);
                            waitingReadersTail = parent._waitingReadersTail;
                            if (waitingReadersTail == null)
                            {
                                return(true);
                            }
                            parent._waitingReadersTail = null;
                        }
                    }
                    else if (count < parent._bufferedCapacity)
                    {
                        // There's room in the channel.  Since we're not transitioning from 0-to-1 and
                        // since there's room, we can simply store the item and exit without having to
                        // worry about blocked/waiting readers.
                        parent._items.EnqueueTail(item);
                        return(true);
                    }
                    else if (parent._mode == BoundedChannelFullMode.Wait)
                    {
                        // The channel is full and we're in a wait mode.
                        // Simply exit and let the caller know we didn't write the data.
                        return(false);
                    }
                    else if (parent._mode == BoundedChannelFullMode.DropWrite)
                    {
                        // The channel is full.  Just ignore the item being added
                        // but say we added it.
                        return(true);
                    }
                    else
                    {
                        // The channel is full, and we're in a dropping mode.
                        // Drop either the oldest or the newest and write the new item.
                        if (parent._mode == BoundedChannelFullMode.DropNewest)
                        {
                            parent._items.DequeueTail();
                        }
                        else
                        {
                            parent._items.DequeueHead();
                        }
                        parent._items.EnqueueTail(item);
                        return(true);
                    }
                }

                // We either wrote the item already, or we're transferring it to the blocked reader we grabbed.
                if (blockedReader != null)
                {
                    Debug.Assert(waitingReadersTail == null, "Shouldn't have any waiters to wake up");

                    // Transfer the written item to the blocked reader.
                    bool success = blockedReader.TrySetResult(item);
                    Debug.Assert(success, "We should always be able to complete the reader.");
                }
                else
                {
                    // We stored an item bringing the count up from 0 to 1.  Alert
                    // any waiting readers that there may be something for them to consume.
                    // Since we're no longer holding the lock, it's possible we'll end up
                    // waking readers that have since come in.
                    ChannelUtilities.WakeUpWaiters(ref waitingReadersTail, result: true);
                }

                return(true);
            }