准确理解 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);
}