SslStream.WriteAsync"当另一个写入操作挂起时,无法调用BeginWrite方法"

Dan*_*gen 4 c# sslstream

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

The BeginWrite method cannot be called when another write operation is pending
Run Code Online (Sandbox Code Playgroud)

mycode的

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

and*_*i m 10

确切了解await关键字的作用非常重要:

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

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

    await SslStream.WriteAsync(buffer, 0, buffer.Length);
Run Code Online (Sandbox Code Playgroud)

使用等待您仅在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);
    }
Run Code Online (Sandbox Code Playgroud)

并通过使用await调用它来等待它的完成:

    await Send(sslStream, message);
Run Code Online (Sandbox Code Playgroud)

如果您不尝试从多个线程写入数据,这应该有效.

此外,还有一些代码可以防止多个线程的写操作重叠(如果与代码正确集成).它使用中间队列和异步编程模型(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);
    }
Run Code Online (Sandbox Code Playgroud)