private Task SendFrameLockAcquiredNonCancelableAsync(MessageOpcode opcode, bool endOfMessage, ArraySegment<byte> payloadBuffer)
{
Debug.Assert(_sendFrameAsyncLock.CurrentCount == 0, "Caller should hold the _sendFrameAsyncLock");
// If we get here, the cancellation token is not cancelable so we don't have to worry about it,
// and we own the semaphore, so we don't need to asynchronously wait for it.
Task writeTask = null;
bool releaseSemaphore = true;
try
{
// Write the payload synchronously to the buffer, then write that buffer out to the network.
int sendBytes = WriteFrameToSendBuffer(opcode, endOfMessage, payloadBuffer);
writeTask = _stream.WriteAsync(_sendBuffer, 0, sendBytes, CancellationToken.None);
// If the operation happens to complete synchronously (or, more specifically, by
// the time we get from the previous line to here, release the semaphore, propagate
// exceptions, and we're done.
if (writeTask.IsCompleted)
{
writeTask.GetAwaiter().GetResult(); // propagate any exceptions
return Task.CompletedTask;
}
// Up until this point, if an exception occurred (such as when accessing _stream or when
// calling GetResult), we want to release the semaphore. After this point, the semaphore needs
// to remain held until writeTask completes.
releaseSemaphore = false;
}
catch (Exception exc)
{
return Task.FromException(_state == WebSocketState.Aborted ?
CreateOperationCanceledException(exc) :
new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc));
}
finally
{
if (releaseSemaphore)
{
_sendFrameAsyncLock.Release();
}
}
// The write was not yet completed. Create and return a continuation that will
// release the semaphore and translate any exception that occurred.
return writeTask.ContinueWith((t, s) =>
{
var thisRef = (ManagedWebSocket)s;
thisRef._sendFrameAsyncLock.Release();
try { t.GetAwaiter().GetResult(); }
catch (Exception exc)
{
throw thisRef._state == WebSocketState.Aborted ?
CreateOperationCanceledException(exc) :
new WebSocketException(WebSocketError.ConnectionClosedPrematurely, exc);
}
}, this, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}