如何在调用 HttpListener.GetContextAsync() 后正确取消请求?

How to correctly cancel requests after calling HttpListener.GetContextAsync()?

我正在努力以不产生 IO 异常的方式关闭 HttpListener。

我似乎以一种不寻常的方式使用了 HttpListener,或者遗漏了一些明显的东西。

我正在设置一个 HttpListener 来侦听来自 OAuth2.0 登录的重定向。 我正在尝试启动侦听器,然后打开 Web 浏览器控件进行登录,然后在执行重定向后关闭侦听器。

代码还没有完成所有这些,因为我正在尝试先修复此错误。

如果我关闭登录 window 以中止登录,然后重试但再次中止,我会收到此错误:

The I/O operation has been aborted because of either a thread exit or an application request

这是堆栈跟踪:

System.Net.HttpListener.EndGetContext(IAsyncResult asyncResult)
System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
BridgeIT.cOAuth2.VB$StateMachine_49_ListenForAuth.MoveNext() in xxx\cOAuth2.vb:line 235

我试过使用 Begin/EndGetContext() 方法,但它们产生的结果相同。

我错过了什么?

这是我从按钮单击事件调用的登录函数:

Public Function Login(ByVal vsLoginUrl As String, ByVal vsClientID As String, ByVal voScopes As IEnumerable(Of String)) As Boolean
        Dim bSuccess As Boolean = False
        Dim oAR As AuthenticationResult = Nothing
        Dim frm As frmOAuth2LoginDlg

        Dim sResponseType As String = "code"
        Dim sScope As String = String.Join(" ", voScopes)
        Dim sState As String = gsPracticeID
        Dim sCodeChallenge As String = GenerateCodeChallenge()
        Dim sCodeChallengeMethod As String = "S256"

        'Append query string params
        vsLoginUrl = $"{vsLoginUrl}?response_type={sResponseType}&client_id={vsClientID}&redirect_uri={msRedirectURL}&scope={sScope}&state={sState}&code_challenge={sCodeChallenge}&code_challenge_method={sCodeChallengeMethod}"
        Try
            'Listen for the the oAuth redirect
            ListenForAuth()

            'Open web browser control
            frm = New frmOAuth2LoginDlg(vsLoginUrl)
            frm.ShowDialog()

            'Do something after logging in

        Catch ex As Exception
            ShowError(Err.Number, Err.Description, ex)

        Finally
            CloseRedirectListener()
            DisposeOfObject(frm)
        End Try

    End Function

监听函数:

Private Async Sub ListenForAuth()
        Dim oContext As HttpListenerContext
        Dim oRequest As HttpListenerRequest
        Dim oResponse As HttpListenerResponse

        moRedirectListener = New HttpListener
        moRedirectListener.Prefixes.Add(msRedirectURL)
        moRedirectListener.Start()

        Try
            oContext = Await moRedirectListener.GetContextAsync()

            oRequest = oContext.Request
            
            'Do something with the request

        Catch oex As ObjectDisposedException
            ' Safely ignore this if the listener has been closed
            ' Else rethrow the exception
            If oex.ObjectName <> "System.Net.HttpListener" Then
                Throw
            End If

        Catch ex As Exception
            ShowError(Err.Number, Err.Description, ex)
        End Try
    End Sub

以及 CloseRedirectListener 函数:

    Private Sub CloseRedirectListener()
        If moRedirectListener IsNot Nothing Then
            moRedirectListener.Stop()
            moRedirectListener.Prefixes.Remove(msRedirectURL)
            moRedirectListener.Close()
        End If
    End Sub

在这个问题上似乎没有太多进展,所以我会 post 将我的解决方案提供给其他人,直到提供更好的解决方案。

捕获特定异常是我发现的异步处理已取消请求的唯一方法:

Private Async Function ListenForAuth(frm As frmOAuth2LoginDlg) As Task
        Dim oContext As HttpListenerContext
        Dim oRequest As HttpListenerRequest

        moRedirectListener = New HttpListener

        moRedirectListener.Prefixes.Add(RedirectURL)
        moRedirectListener.Start()

        Try
            oContext = Await moRedirectListener.GetContextAsync()
            oRequest = oContext.Request

            ' Do something with the request

        ' Handle the exceptions in the case of cancelled requests
        Catch oObjectDisposedException As ObjectDisposedException
            ' If the Listener has been closed then safely ignore this
            ' Else rethrow
            If oObjectDisposedException.ObjectName <> "System.Net.HttpListener" Then
                Throw
            End If

        Catch oHttpListenerException As System.Net.HttpListenerException
            ' If the Listener has been stopped then safely ignore this
            ' Else rethrow
            If oHttpListenerException.Message <> "The I/O operation has been aborted because Of either a thread Exit Or an application request" Then
                Throw
            End If

        Catch ex As Exception
            ' Do something with other exceptions

        Finally
            If Application.OpenForms.OfType(Of frmOAuth2LoginDlg).Count > 0 Then
                frm.Close()
            End If

        End Try

    End Function