await キーワードの機能を正確に理解することが重要です。
await 式は、実行中のスレッドをブロックしません。代わりに、コンパイラは、待機中のタスクの継続として非同期メソッドの残りをサインアップします。その後、制御は非同期メソッドの呼び出し元に戻ります。タスクが完了すると、その継続が呼び出され、非同期メソッドの実行が中断したところから再開されます ( MSDN - await (C# リファレンス) )。
null 以外のバッファで 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);
}