/// <summary>
        /// Copy data into the buffers.
        /// </summary>
        /// <param name="data">An array containing the data to copy</param>
        /// <param name="offset">The starting index of the data to copy</param>
        /// <param name="count">The number of data elements to copy</param>
        public void CopyDataToBuffer(DataType[] data, int offset, int count)
        {
            lock (_lockObject)
            {
                EnsureSpaceInInputBuffer(count);

                CopyDataToInputBuffer(data, offset, count);

                if (HasAsyncReader && AdvanceToNextOutputBuffer())
                {
                    GiveBufferToAsyncReader(OutputBuffer.CreateArraySegment());
                }
            }
        }
        /// <summary>
        /// Synchronously reads data from the buffer.
        /// </summary>
        /// <returns>
        /// An ArraySegment containing buffered data, or an empty buffer if there is no
        /// data to read.
        /// </returns>
        /// <remarks>
        /// The ArraySegment remains valid until GetBufferedData/Async is called again.
        /// After the next call to this method, the buffer in the ArraySegment can be
        /// reused for future data.
        /// </remarks>
        public ArraySegment <DataType> GetBufferedData()
        {
            lock (_lockObject)
            {
                if (AdvanceToNextOutputBuffer())
                {
                    return(OutputBuffer.CreateArraySegment());
                }
                else
                {
                    SignalBufferEmpty();

                    return(default(ArraySegment <DataType>));
                }
            }
        }
        /// <summary>
        /// Asynchronously reads data from the buffer.
        /// </summary>
        /// <returns>
        /// A task that returns an ArraySegment containing buffered data. If there
        /// is no buffered data, the task does not complete until more data is
        /// buffered, or the buffers are disposed.
        /// </returns>
        /// <remarks>
        /// The ArraySegment remains valid until GetBufferedData/Async is called again.
        /// After the next call to this method, the buffer in the ArraySegment can be
        /// reused for future data.
        /// </remarks>
        public Task <ArraySegment <DataType> > GetBufferedDataAsync()
        {
            lock (_lockObject)
            {
                if (AdvanceToNextOutputBuffer())
                {
                    return(Task.FromResult(OutputBuffer.CreateArraySegment()));
                }
                else
                {
                    _getBufferedData = new TaskCompletionSource <ArraySegment <DataType> >();

                    // We need a local reference to the task here, because SignalBufferEmpty()
                    // could end up calling Dispose(), and that will complete and then clear
                    // _getBufferedData. We still want to return the completed task to the caller
                    // of this method.
                    Task <ArraySegment <DataType> > task = _getBufferedData.Task;

                    SignalBufferEmpty();

                    return(task);
                }
            }
        }