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();
                }
            }
        }