/// <summary> /// The caller indicates that the offer is now registered in all places /// </summary> public void ProbePhaseComplete() { // If there is no timeout, do nothing // If we are already completed, do nothing if (m_timeout == Timeout.InfiniteDateTime || IsTaken) { return; } // If the timeout has occurred, set the timeout else if (m_timeout < DateTime.Now) { ExpirationCallback(); } // Register the timeout callback else { ExpirationManager.AddExpirationCallback(m_timeout, ExpirationCallback); } }
/// <summary> /// Registers a desire to write to the channel /// </summary> /// <param name="offer">A callback method for offering an item, use null to unconditionally accept</param> /// <param name="value">The value to write to the channel.</param> /// <param name="timeout">The time to wait for the operation, use zero to return a timeout immediately if no items can be read. Use a negative span to wait forever.</param> public async Task WriteAsync(T value, TimeSpan timeout, ITwoPhaseOffer offer = null) { var wr = new WriterEntry(offer, new TaskCompletionSource <bool>(), timeout.Ticks <= 0 ? Timeout.InfiniteDateTime : DateTime.Now + timeout, value); using (await m_asynclock.LockAsync()) { if (m_isRetired) { ThreadPool.QueueItem(() => wr.Source.SetException(new RetiredException())); await wr.Source.Task; return; } m_writerQueue.Add(wr); if (!await MatchReadersAndWriters(false, wr.Source.Task)) { System.Diagnostics.Debug.Assert(m_writerQueue[m_writerQueue.Count - 1].Source == wr.Source); // If we have a buffer slot to use if (m_writerQueue.Count <= m_bufferSize && m_retireCount < 0) { if (offer == null || await offer.OfferAsync(this)) { if (offer != null) { await offer.CommitAsync(this); } m_writerQueue[m_writerQueue.Count - 1] = new WriterEntry(null, null, Timeout.InfiniteDateTime, value); wr.Source.TrySetResult(true); } else { wr.Source.TrySetCanceled(); } } else { // If this was a probe call, return a timeout now if (timeout.Ticks >= 0 && wr.Expires < DateTime.Now) { m_writerQueue.RemoveAt(m_writerQueue.Count - 1); ThreadPool.QueueItem(() => wr.Source.SetException(new TimeoutException())); } else { // Make room if we have too many if (m_maxPendingWriters > 0 && (m_writerQueue.Count - m_bufferSize - 1) >= m_maxPendingWriters) { switch (m_pendingWritersOverflowStrategy) { case QueueOverflowStrategy.FIFO: { var exp = m_writerQueue[m_bufferSize].Source; m_writerQueue.RemoveAt(m_bufferSize); if (exp != null) { ThreadPool.QueueItem(() => exp.TrySetException(new ChannelOverflowException())); } } break; case QueueOverflowStrategy.LIFO: { var exp = m_writerQueue[m_writerQueue.Count - 2].Source; m_writerQueue.RemoveAt(m_writerQueue.Count - 2); if (exp != null) { ThreadPool.QueueItem(() => exp.TrySetException(new ChannelOverflowException())); } } break; case QueueOverflowStrategy.Reject: default: { var exp = m_writerQueue[m_writerQueue.Count - 1].Source; m_writerQueue.RemoveAt(m_writerQueue.Count - 1); if (exp != null) { ThreadPool.QueueItem(() => exp.TrySetException(new ChannelOverflowException())); } await wr.Source.Task; } return; } } // If we have expanded the queue with a new batch, see if we can purge old entries m_writerQueueCleanup = await PerformQueueCleanupAsync(m_writerQueue, true, m_writerQueueCleanup); if (wr.Expires != Timeout.InfiniteDateTime) { ExpirationManager.AddExpirationCallback(wr.Expires, () => ExpireItemsAsync().FireAndForget()); } } } } } await wr.Source.Task; return; }
/// <summary> /// Registers a desire to read from the channel /// </summary> /// <param name="offer">A callback method for offering an item, use null to unconditionally accept</param> /// <param name="timeout">The time to wait for the operation, use zero to return a timeout immediately if no items can be read. Use a negative span to wait forever.</param> public async Task <T> ReadAsync(TimeSpan timeout, ITwoPhaseOffer offer = null) { var rd = new ReaderEntry(offer, new TaskCompletionSource <T>(), timeout.Ticks <= 0 ? Timeout.InfiniteDateTime : DateTime.Now + timeout); using (await m_asynclock.LockAsync()) { if (m_isRetired) { ThreadPool.QueueItem(() => rd.Source.SetException(new RetiredException())); return(await rd.Source.Task); } m_readerQueue.Add(rd); if (!await MatchReadersAndWriters(true, rd.Source.Task)) { System.Diagnostics.Debug.Assert(m_readerQueue[m_readerQueue.Count - 1].Source == rd.Source); // If this was a probe call, return a timeout now if (timeout.Ticks >= 0 && rd.Expires < DateTime.Now) { m_readerQueue.RemoveAt(m_readerQueue.Count - 1); ThreadPool.QueueItem(() => rd.Source.TrySetException(new TimeoutException())); } else { // Make room if we have too many if (m_maxPendingReaders > 0 && (m_readerQueue.Count - 1) >= m_maxPendingReaders) { switch (m_pendingReadersOverflowStrategy) { case QueueOverflowStrategy.FIFO: { var exp = m_readerQueue[0].Source; m_readerQueue.RemoveAt(0); ThreadPool.QueueItem(() => exp.TrySetException(new ChannelOverflowException())); } break; case QueueOverflowStrategy.LIFO: { var exp = m_readerQueue[m_readerQueue.Count - 2].Source; m_readerQueue.RemoveAt(m_readerQueue.Count - 2); ThreadPool.QueueItem(() => exp.TrySetException(new ChannelOverflowException())); } break; case QueueOverflowStrategy.Reject: default: { var exp = m_readerQueue[m_readerQueue.Count - 1].Source; m_readerQueue.RemoveAt(m_readerQueue.Count - 1); ThreadPool.QueueItem(() => exp.TrySetException(new ChannelOverflowException())); await rd.Source.Task; } return(await rd.Source.Task); } } // If we have expanded the queue with a new batch, see if we can purge old entries m_readerQueueCleanup = await PerformQueueCleanupAsync(m_readerQueue, true, m_readerQueueCleanup); if (rd.Expires != Timeout.InfiniteDateTime) { ExpirationManager.AddExpirationCallback(rd.Expires, () => ExpireItemsAsync().FireAndForget()); } } } } return(await rd.Source.Task); }