开发者

Socket/threading problem: The Undo operation encountered a context that is different from what was applied in the corresponding Set operation

开发者 https://www.devze.com 2023-01-17 17:37 出处:网络
I\'m having problems with the above much asked-about error. We have a TCP/IP server application which has been working fine for some years. I now need to allow the application to accept connections fr

I'm having problems with the above much asked-about error. We have a TCP/IP server application which has been working fine for some years. I now need to allow the application to accept connections from directly connected USB devices, by internally using a socket connection to patch through to localhost (127.0.0.1) within the server application. (BTW I mention the USB only to explain why I am doing this - I disabled all USB functions as part of debugging this problem).

The communications along this socket can result in calls to GUI elements on both the client and the server side. Accesses to GUI elements on the client side cause the error in the title (call stack below). One of the key problems here is that the debugger is unable to halt on the exception: despite all exceptions being set to halt when thrown the application simply terminates when the error occurs.

The only thing that seems unique about my application is that it uses an internal socket to connect to 127.0.0.1. I have also confirmed that the application works fine if the client is separated into a separate application. However, I can't use this as a permanent solution for other reasons.

There are several posts discussing this sort of problem which I have listed below. Unfortunately none seem to provide a solution in my case:

  • Most related posts discuss the need to ensure that all GUI operations are performed on the GUI thread, by using Invoke or BeginInvoke. I am confident my application does this correctly (it obtains a form using Application.Forms to get the main form and calls Invoke on this) and have double checked in the debugger.
  • Relating to the above, there is some discussion as to the use of Invoke vs BeginInvoke in order to block/not block. In my case both have the same result.
  • Some posts suggest it is necessary to create the sockets themselves on the GUI thread (mine are).
  • This one explains that you can get the error if you use DoEvents in your application (I don't).
  • This one also implies that you could get the error with a missing EndConnect call when using asynchronous calls for client socket connection (my client connection is synchronous).
  • This one explains that you can get incorrect results from InvokeRequired if the window handle is not yet created (have checked this with IsHandleCreated).
  • This one on microsoft connect reports a similar sounding bug but doesnt have a solution (microsoft have been 'investigating' it since 2006!)
  • This one contains a suggestion to use AsyncOperationManager.SynchronizationContext to backup/restore the synchronsiation context, which (unsurprisingly?) just causes different errors.
  • There are a couple of posts that suggest that the error is debug only, and the following will make it go away - but I haven't bothered trying that:

    System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false

There are other posts asking similar questions: here, here and here. A good one here too.

Here is a code snippet - this causes a crash within ProcessCommandCT when socket data is received by the client:

' 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 开发者_如何学GoSub 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

I'm using VB .NET 2010 with .NET4.

Thanks for any help - I hope the consolidated list of posts above also helps others.

Tim

Call stack:

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).


This exception occurs when a thread's ExecutionContext property changes. Specifically when that thread is a threadpool or I/O completion thread that executes a callback and it acquired its ExecutionContext from another thread that made a BeginXxx call to start an asynchronous operation. Like Socket.BeginReceive().

There's ample of opportunity for this to happen in the posted code since it tinkers with forms in the callback. ExecutionContext has a hidden property named SynchronizationContext which keeps track of SynchronizationContext.Current. Winforms installs a custom synchronization provider the first time any form is created. Required to properly marshal calls from a worker thread to the UI thread. It is a class derived from SynchronizationContext named WindowsFormsSynchronizationContext.

The likely failure mode therefore is that the sock_Receive() method is called before any Winforms forms are created. With the form creation code installing the synchronization provider and altering the ExecutionContext and thus crashing the code with the exception. Such a problem needs to be fixed by altering the initialization of the app, ensuring that a main form exists before you allow any asynchronous code to use BeginInvoke().


Can you show the code where you are issuing and ending IO?

Here is a possible workaround: Let's set the synchronizationcontext.current to null while you are beginning all IO operations. It looks like something in the .NET framework gets confused and is trying to restore the executioncontext twice.

Here is some useful helper:

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

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

Call it like this:

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

Another question: Are you nesting IO calls? Are you issuing many small IOs on this socket? I am asking this because the framework has a special case here:

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

This piece of code gives rise to the suspicion that there is a bug in the .NET framework in this rarely hit case. ThreadPool.QueueUserWorkItem will capture the current ExecutionContext and restore it later which is what we are seeing in the stack trace.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号