【问题标题】:SslStream.WriteAsync "The BeginWrite method cannot be called when another write operation is pending"SslStream.WriteAsync“当另一个写入操作挂起时,无法调用 BeginWrite 方法”
【发布时间】:2012-09-26 23:15:27
【问题描述】:

异步向客户端写入数据时如何避免这个问题

The BeginWrite method cannot be called when another write operation is pending

我的密码

public async void Send(byte[] buffer)
{
    if (buffer == null)
        return;
    await SslStream.WriteAsync(buffer, 0, buffer.Length);
}

【问题讨论】:

  • 你能发布你的代码吗?我认为当您收到错误时,您的异步写入操作会重叠。
  • 你是从单线程调用这个方法吗?
  • 我认为使用异步我不用担心线程?!

标签: c# sslstream


【解决方案1】:

准确理解 await 关键字的作用很重要:

await 表达式不会阻塞它所在的线程 执行。相反,它会导致编译器注册其余的 async 方法作为等待任务的延续。然后控制 返回到异步方法的调用者。当任务完成时,它 调用其延续,并继续执行异步方法 它停止的地方 (MSDN - await (C# Reference))。

当您使用一些非空缓冲区调用 Send 时,您将到达

    await SslStream.WriteAsync(buffer, 0, buffer.Length);

使用 await 您只能在 Send 方法中阻止执行,但即使 WriteAsync 尚未完成,调用者中的代码也会继续执行。现在,如果在 WriteAsync 完成之前再次调用 Send 方法,您将收到您发布的异常,因为 SslStream 不允许多次写入操作,并且您发布的代码不会阻止这种情况的发生。

如果您想确保之前的 BeginWrite 已经完成,您必须更改 Send 方法以返回一个 Task

    async Task Send(SslStream sslStream, byte[] buffer)
    {
        if (buffer == null)
            return;

        await sslStream.WriteAsync(buffer, 0, buffer.Length);
    }

并通过使用 await 调用它来等待它完成,例如:

    await Send(sslStream, message);

如果您不尝试从多个线程写入数据,这应该可以工作。

这里还有一些代码可以防止来自多个线程的写操作重叠(如果与您的代码正确集成)。它使用中间队列和异步编程模型(APM)并且运行速度非常快。 您需要调用 EnqueueDataForWrite 来发送数据。

    ConcurrentQueue<byte[]> writePendingData = new ConcurrentQueue<byte[]>();
    bool sendingData = false;

    void EnqueueDataForWrite(SslStream sslStream, byte[] buffer)
    {
        if (buffer == null)
            return;

        writePendingData.Enqueue(buffer);

        lock (writePendingData)
        {
            if (sendingData)
            {
                return;
            }
            else
            {
                sendingData = true;
            }
        }

        Write(sslStream);            
    }

    void Write(SslStream sslStream)
    {
        byte[] buffer = null;
        try
        {
            if (writePendingData.Count > 0 && writePendingData.TryDequeue(out buffer))
            {
                sslStream.BeginWrite(buffer, 0, buffer.Length, WriteCallback, sslStream);
            }
            else
            {
                lock (writePendingData)
                {
                    sendingData = false;
                }
            }
        }
        catch (Exception ex)
        {
            // handle exception then
            lock (writePendingData)
            {
                sendingData = false;
            }
        }            
    }

    void WriteCallback(IAsyncResult ar)
    {
        SslStream sslStream = (SslStream)ar.AsyncState;
        try
        {
            sslStream.EndWrite(ar);
        }
        catch (Exception ex)
        {
            // handle exception                
        }

        Write(sslStream);
    }

【讨论】:

  • 很好的解释和例子安德烈,谢谢。您防止多线程写入的代码运行良好,我一直在尝试编写非常相似的代码:)。
  • 非常好。对我帮助很大。
【解决方案2】:

如果操作是使用 BeginWrite 启动的,请在开始下一个操作之前调用 SslStream.EndWrite 以结束旧的写入操作。如果操作是使用WriteAsync 启动的,请确保任务首先完成,例如使用await 关键字或Wait()

【讨论】:

  • 我用这种方式,但它继续告诉我同样的信息
  • 等待 SslStream.WriteAsync(buffer, 0, buffer.Length)
  • 还有其他东西在使用流而不是清理或超时吗?根据social.msdn.microsoft.com/forums/en-US/netfxnetcom/thread/…,SslStream 类有意防止多个同时读取和写入。
  • 只有 BeginRead,没有其他 BeginWrites !
  • 我也在尝试 MemoryStream 的 await s.CopyToAsync(SslStream);。但是同样的错误让我发疯了
【解决方案3】:

其他答案很好,但我认为更简洁的解释(这是我在搜索相同错误消息时正在寻找的)是这样的:

该方法不是线程安全的,而是以非线程安全的方式调用的,因为您不是 awaiting 您的 Send 方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-22
    • 1970-01-01
    • 2019-10-16
    • 2017-05-17
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    • 2016-04-25
    相关资源
    最近更新 更多