public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options) { _operation.InterlockedCheckAndUpdateState(WebSocketState.Connecting, s_validConnectStates); using (CancellationTokenRegistration ctr = ThrowOrRegisterCancellation(cancellationToken)) { lock (_operation.Lock) { _operation.CheckValidState(s_validConnectingStates); // Must grab lock until RequestHandle is populated, otherwise we risk resource leaks on Abort. // // TODO (Issue 2506): Alternatively, release the lock between WinHTTP operations and check, under lock, that the // state is still valid to continue operation. _operation.SessionHandle = InitializeWinHttp(options); _operation.ConnectionHandle = Interop.WinHttp.WinHttpConnectWithCallback( _operation.SessionHandle, uri.IdnHost, (ushort)uri.Port, 0); ThrowOnInvalidHandle(_operation.ConnectionHandle); bool secureConnection = uri.Scheme == UriScheme.Https || uri.Scheme == UriScheme.Wss; _operation.RequestHandle = Interop.WinHttp.WinHttpOpenRequestWithCallback( _operation.ConnectionHandle, "GET", uri.PathAndQuery, null, Interop.WinHttp.WINHTTP_NO_REFERER, null, secureConnection ? Interop.WinHttp.WINHTTP_FLAG_SECURE : 0); ThrowOnInvalidHandle(_operation.RequestHandle); _operation.IncrementHandlesOpenWithCallback(); if (!Interop.WinHttp.WinHttpSetOption( _operation.RequestHandle, Interop.WinHttp.WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, IntPtr.Zero, 0)) { WinHttpException.ThrowExceptionUsingLastError(); } // We need the address of the IntPtr to the GCHandle. IntPtr context = _operation.ToIntPtr(); IntPtr contextAddress; unsafe { contextAddress = (IntPtr)(void *)&context; } if (!Interop.WinHttp.WinHttpSetOption( _operation.RequestHandle, Interop.WinHttp.WINHTTP_OPTION_CONTEXT_VALUE, contextAddress, (uint)IntPtr.Size)) { WinHttpException.ThrowExceptionUsingLastError(); } const uint notificationFlags = Interop.WinHttp.WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | Interop.WinHttp.WINHTTP_CALLBACK_FLAG_HANDLES | Interop.WinHttp.WINHTTP_CALLBACK_FLAG_SECURE_FAILURE; if (Interop.WinHttp.WinHttpSetStatusCallback( _operation.RequestHandle, WinHttpWebSocketCallback.s_StaticCallbackDelegate, notificationFlags, IntPtr.Zero) == (IntPtr)Interop.WinHttp.WINHTTP_INVALID_STATUS_CALLBACK) { WinHttpException.ThrowExceptionUsingLastError(); } _operation.RequestHandle.AttachCallback(); // We need to pin the operation object at this point in time since the WinHTTP callback // has been fully wired to the request handle and the operation object has been set as // the context value of the callback. Any notifications from activity on the handle will // result in the callback being called with the context value. _operation.Pin(); AddRequestHeaders(uri, options); _operation.TcsUpgrade = new TaskCompletionSource <bool>(); } await InternalSendWsUpgradeRequestAsync().ConfigureAwait(false); await InternalReceiveWsUpgradeResponse().ConfigureAwait(false); lock (_operation.Lock) { VerifyUpgradeResponse(); ThrowOnInvalidConnectState(); _operation.WebSocketHandle = Interop.WinHttp.WinHttpWebSocketCompleteUpgrade(_operation.RequestHandle, IntPtr.Zero); ThrowOnInvalidHandle(_operation.WebSocketHandle); _operation.IncrementHandlesOpenWithCallback(); // We need the address of the IntPtr to the GCHandle. IntPtr context = _operation.ToIntPtr(); IntPtr contextAddress; unsafe { contextAddress = (IntPtr)(void *)&context; } if (!Interop.WinHttp.WinHttpSetOption( _operation.WebSocketHandle, Interop.WinHttp.WINHTTP_OPTION_CONTEXT_VALUE, contextAddress, (uint)IntPtr.Size)) { WinHttpException.ThrowExceptionUsingLastError(); } _operation.WebSocketHandle.AttachCallback(); _operation.UpdateState(WebSocketState.Open); if (_operation.RequestHandle != null) { _operation.RequestHandle.Dispose(); // RequestHandle will be set to null in the callback. } _operation.TcsUpgrade = null; ctr.Dispose(); } } }