5

将数据异步写入客户端时如何防止此问题

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);
}
4

3 回答 3

11

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

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

当您使用一些非空缓冲区调用 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);
    }
于 2012-09-29T01:19:57.977 回答
2

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

于 2012-09-26T23:18:17.107 回答
0

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

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

于 2020-02-13T23:33:10.917 回答