首页 > 解决方案 > 调用 HttpListener.GetContextAsync() 后如何正确取消请求?

问题描述

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

看来我要么以不寻常的方式使用 HttpListener,要么遗漏了一些明显的东西。

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

代码还没有完成所有这些,因为我试图先修复这个错误。

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

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

标签: .netvb.netasync-awaithttplistener

解决方案


在这个问题上似乎没有太多动静,所以我会为其他人发布我的解决方案,直到提供更好的解决方案。

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

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

推荐阅读