8

我遇到了上述很多问题的问题。我们有一个多年来一直运行良好的 TCP/IP 服务器应用程序。我现在需要允许应用程序接受来自直接连接的 USB 设备的连接,方法是在内部使用套接字连接修补服务器应用程序中的 localhost (127.0.0.1)。(顺便说一句,我提到 USB 只是为了解释我这样做的原因 - 作为调试此问题的一部分,我禁用了所有 USB 功能)。

沿着这个套接字的通信会导致在客户端和服务器端调用 GUI 元素。在客户端访问 GUI 元素会导致标题中的错误(下面的调用堆栈)。这里的关键问题之一是调试器无法停止异常:尽管所有异常都设置为在抛出时停止,但应用程序只是在错误发生时终止。

我的应用程序唯一看起来独特的是它使用内部套接字连接到 127.0.0.1。我还确认如果客户端被分离到一个单独的应用程序中,该应用程序可以正常工作。但是,由于其他原因,我不能将其用作永久解决方案。

有几篇文章讨论了我在下面列出的这类问题。不幸的是,在我的情况下似乎没有人提供解决方案:

  • 大多数相关帖子都讨论了使用 Invoke 或 BeginInvoke 确保所有 GUI 操作都在 GUI 线程上执行的必要性。我相信我的应用程序可以正确执行此操作(它使用 Application.Forms 获取表单以获取主表单并在此调用 Invoke)并在调试器中进行了双重检查。
  • 与上述相关,有一些关于使用 Invoke 与 BeginInvoke 来阻止/不阻止的讨论。在我的情况下,两者都有相同的结果。
  • 一些帖子建议有必要在 GUI 线程上自己创建套接字(我的是)。
  • 解释了如果您在应用程序中使用 DoEvents 会出现错误(我没有)。
  • 也意味着在对客户端套接字连接使用异步调用(我的客户端连接是同步的)时,您可能会收到缺少 EndConnect 调用的错误。
  • 解释了如果尚未创建窗口句柄,您可能会从 InvokeRequired 获得不正确的结果(已使用 IsHandleCreated 进行了检查)。
  • microsoft connect 上的这个报告了一个类似的错误,但没有解决方案(微软自 2006 年以来一直在“调查”它!)
  • 这个包含使用 AsyncOperationManager.SynchronizationContext 备份/恢复同步上下文的建议,这(不出所料?)只会导致不同的错误。
  • 有几篇文章表明错误只是调试,以下将使它消失 - 但我没有费心尝试:
    System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false

还有其他帖子提出了类似的问题:hereherehere这里也不错。

这是一个代码片段 - 当客户端接收到套接字数据时,这会导致 ProcessCommandCT 崩溃:

' Find application main form from any thread
' There is only one instance of 'RibbonForm1' and this is the main form
Public Function GetRibbonForm() As RibbonForm1
    Dim rf As RibbonForm1 = Nothing
    For Each f As Form In My.Application.OpenForms
        rf = TryCast(f, RibbonForm1)
        If rf IsNot Nothing Then Return rf
    Next
    Return Nothing
End Function

Public Sub ProcessCommandCT(ByVal cmd As String)
    ' code is peppered with these to debug this problem
    Debug.Assert(GetRibbonForm.IsHandleCreated)
    Debug.Assert(Not GetRibbonForm.InvokeRequired)
    Try
        Select Case cmd
            Case "MYCMD"
                Dim f As New Form 
                f.ShowDialog()
        End Select
    Catch ex As Exception
        MsgBox(ex.ToString)
    End Try

End Sub

Private Sub sock_Receive(ByVal msg As String) Handles sck.Receive
    Dim rf As RibbonForm1 = GetRibbonForm
    If rf.InvokeRequired Then
        rf.BeginInvoke(New SubWithStringArgDelegate(AddressOf ProcessCommandCT), New Object() {msg})
    Else
        ProcessCommandCT(msg)
    End If
End Sub

我正在使用带有 .NET4 的 VB .NET 2010。

感谢您的帮助 - 我希望上述帖子的综合列表也对其他人有所帮助。

蒂姆

调用堆栈:

The thread '<No Name>' (0x148c) has exited with code 0 (0x0).
System.Transactions Critical: 0 : <TraceRecord    xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Critical"><TraceIdentifier>http://msdn.microsoft.com/TraceCodes/System/ActivityTracing/2004/07/Reliability/Exception/Unhandled</TraceIdentifier><Description>Unhandled exception</Description><AppDomain>myapp.vshost.exe</AppDomain><Exception><ExceptionType>System.InvalidOperationException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>The Undo operation encountered a context that is different from what was applied in the corresponding Set operation. The possible cause is that a context was Set on the thread and not reverted(undone).</Message><StackTrace>   at System.Threading.SynchronizationContextSwitcher.Undo()
at System.Threading.ExecutionContextSwitcher.Undo()
at System.Threading.ExecutionContext.runFinallyCode(Object userData, Boolean exceptionThrown)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteBackoutCodeHelper(Object backoutCode, Object userData, Boolean exceptionThrown)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Net.ContextAwareResult.Complete(IntPtr userToken)
at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
at System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)</StackTrace><ExceptionString>System.InvalidOperationException: The Undo operation encountered a context that is different from what was applied in the corresponding Set operation. The possible cause is that a context was Set on the thread and not reverted(undone).
at System.Threading.SynchronizationContextSwitcher.Undo()
at System.Threading.ExecutionContextSwitcher.Undo()
at System.Threading.ExecutionContext.runFinallyCode(Object userData, Boolean exceptionThrown)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteBackoutCodeHelper(Object backoutCode, Object userData, Boolean exceptionThrown)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Net.ContextAwareResult.Complete(IntPtr userToken)
at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
at System.Net.Sockets.BaseOverlappedAsyncResult.CompletionPortCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)</ExceptionString></Exception></TraceRecord>
The program '[6324] myapp.vshost.exe: Managed (v4.0.30319)' has exited with code 0 (0x0).
4

2 回答 2

2

当线程的 ExecutionContext 属性更改时会发生此异常。特别是当该线程是执行回调的线程池或 I/O 完成线程并且它从另一个进行 BeginXxx 调用以启动异步操作的线程获取其 ExecutionContext 时。就像 Socket.BeginReceive()。

在发布的代码中很有可能发生这种情况,因为它会修改回调中的表单。ExecutionContext 有一个名为 SynchronizationContext 的隐藏属性,它跟踪 SynchronizationContext.Current。Winforms 会在第一次创建任何表单时安装自定义同步提供程序。需要将工作线程的调用正确编组到 UI 线程。它是一个派生自 SynchronizationContext 的类,名为 WindowsFormsSynchronizationContext。

因此,可能的故障模式是在创建任何 Winforms 表单之前调用 sock_Receive() 方法。使用表单创建代码安装同步提供程序并更改 ExecutionContext,从而使代码崩溃并出现异常。需要通过更改应用程序的初始化来解决此类问题,确保在允许任何异步代码使用 BeginInvoke() 之前存在主窗体。

于 2012-04-13T00:51:34.607 回答
0

你能显示你发出和结束 IO 的代码吗?

这是一个可能的解决方法:让我们在开始所有 IO 操作时将同步上下文.current 设置为 null。.NET 框架中的某些东西看起来很混乱,并试图两次恢复执行上下文。

这是一些有用的助手:

    public static void ChangeSynchronizationContext(SynchronizationContext synchronizationContext, Action actionUnderSynchronizationContext)
    {
        var oldSyncContext = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(synchronizationContext);

        try
        {
            actionUnderSynchronizationContext();
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(oldSyncContext);
        }
    }

像这样称呼它:

ChangeSynchronizationContext(null, () => { /* start io */ });

另一个问题:你在嵌套 IO 调用吗?您是否在此套接字上发出许多小型 IO?我问这个是因为框架在这里有一个特殊情况:

        if (currentThreadContext.m_NestedIOCount >= 50)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(this.WorkerThreadComplete));
            flag = true;
        }
        else
        {
            this.m_AsyncCallback(this);
        }

这段代码让人怀疑在这个罕见的案例中 .NET 框架中存在错误。ThreadPool.QueueUserWorkItem 将捕获当前的 ExecutionContext 并稍后恢复它,这就是我们在堆栈跟踪中看到的。

于 2012-04-12T21:43:32.803 回答