异步C#WriteAsync - 在返回之前复制缓冲区吗?

时间:2016-01-05 01:28:28

标签: .net asynchronous pipe

假设我有这个:

public Task WriteByteArray(byte[] pBytes)
{
    return m_pPipeStream.WriteAsync(pBytes, 0, pBytes.Length);
}

其中m_pPipeStream是C#的PipeStream ......

从任何文档或示例中都不清楚底层系统是否在返回之前复制给定的pBytes。换句话说,我需要在写出时保留pBytes,还是内核对它进行快照,然后返回?我不知道。我问这个问题是因为我没有看到任何简单直接解决这个问题的当前问题。

3 个答案:

答案 0 :(得分:1)

异步函数通常在返回之前不进行复制。

然而,你并不需要坚持下去,因为该功能会做到这一点(事实上.NET几乎总能保留它所需的一切)。

您需要做的是确保在它返回之后以及之前更改内容,因为这将使其无法确定哪个版本的数据(或两者的混合使用。

答案 1 :(得分:1)

您不需要继续参考pBytes。 WriteAsync将保持这一点直到需要。您可以立即发布pByte参考。但是,WriteAsync只保留引用而不是pBytes的副本,因此请确保在WriteAsync完成之前不要更改pBytes的字节。

答案 2 :(得分:0)

这听起来不像System.IO.Pipes.PipeStream

如果您使用PipeStream(无论可能是什么)作为异步生产者和消费者流之间的切换点,您实际上可以完全取消它并滚动您自己的并行复制机制。

最简单的形式就是它的样子:

public static async Task ParallelCopyAsync(Stream source, Stream target, int bufferSize = 4096)
{
    byte[] readBuffer = new byte[bufferSize];
    byte[] writeBuffer = new byte[bufferSize];
    Task writeTask = null;
    int bytesRead;

    while ((bytesRead = await source.ReadAsync(readBuffer, 0, readBuffer.Length).ConfigureAwait(false)) != 0)
    {
        if (writeTask != null) {
            await writeTask.ConfigureAwait(false);
        }

        Array.Copy(readBuffer, 0, writeBuffer, 0, bytesRead);

        writeTask = target.WriteAsync(writeBuffer, 0, bytesRead);
    }

    if (writeTask != null) {
        await writeTask.ConfigureAwait(false);
    }
}

但是,因为这需要分配2个缓冲区,我们可以简单地旋转它们(根据我的原始注释),而不是从读缓冲区复制到写缓冲区。有了这个和其他一些优化,我们的管道将如下所示:

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Ardex.IO
{
    public static class StreamUtil
    {
        // Taken from .NET source.
        // Largest multiple of 4096 that is still smaller
        // than the large object heap threshold (85K).
        private const int DefaultCopyBufferSize = 81920;

        public static async Task ParallelCopyAsync(Stream source, Stream target, int bufferSize = DefaultCopyBufferSize, CancellationToken ct = default(CancellationToken))
        {
            if (source == null) throw new ArgumentNullException(nameof(source));
            if (target == null) throw new ArgumentNullException(nameof(target));
            if (bufferSize < 1) throw new ArgumentOutOfRangeException(nameof(bufferSize));

            ct.ThrowIfCancellationRequested();

            byte[] buffer = new byte[bufferSize];
            byte[] standbyBuffer = null; // Allocated only if source is not empty.
            Task writeTask = null;
            int bytesRead;

            while ((bytesRead = await source.ReadAsync(buffer, 0, bufferSize, ct).ConfigureAwait(false)) != 0)
            {
                if (writeTask != null) {
                    await writeTask.ConfigureAwait(false);
                }

                // Any code after WriteAync will potentially execute
                // in parallel with the write. We cannot mutate the
                // buffer until this writeTask instance is awaited.
                writeTask = target.WriteAsync(buffer, 0, bytesRead, ct);

                // Swap buffers.
                byte[] tmp = standbyBuffer;
                standbyBuffer = buffer;
                buffer = tmp ?? new byte[bufferSize];
            }

            if (writeTask != null)
            {
                // Writing final chunk.
                await writeTask.ConfigureAwait(false);
            }
        }
    }
}