Проблема с сокетом / многопоточностью: операция Undo обнаружила контекст, отличный от того, который был применен в соответствующей операции Set - PullRequest
8 голосов
/ 28 сентября 2010

У меня проблемы с вышеупомянутой ошибкой, о которой часто спрашивают.У нас есть серверное приложение TCP / IP, которое отлично работает уже несколько лет.Теперь мне нужно разрешить приложению принимать подключения от напрямую подключенных USB-устройств, внутренне используя сокетное соединение для подключения к локальному узлу (127.0.0.1) внутри серверного приложения.(Кстати, я упоминаю USB только для объяснения, почему я делаю это - я отключил все функции USB как часть отладки этой проблемы).

Связь по этому сокету может привести к вызовам элементов GUI как на стороне клиента, так и на стороне сервера.Доступ к элементам GUI на стороне клиента вызывает ошибку в заголовке (стек вызовов ниже).Одна из ключевых проблем здесь заключается в том, что отладчик не может остановить исключение: несмотря на то, что все исключения, настроенные для остановки при выбросе, приложение просто завершает работу при возникновении ошибки.

Единственное, что кажется уникальным в моемПриложение заключается в том, что он использует внутренний сокет для подключения к 127.0.0.1.Я также подтвердил, что приложение работает нормально, если клиент разделен на отдельное приложение.Однако я не могу использовать это как постоянное решение по другим причинам.

Есть несколько постов, обсуждающих проблему такого рода, которые я перечислил ниже.К сожалению, ни один из них не дает решения в моем случае:

  • В большинстве связанных постов обсуждается необходимость обеспечения выполнения всех операций графического интерфейса в потоке графического интерфейса с помощью Invoke или BeginInvoke.Я уверен, что мое приложение делает это правильно (оно получает форму, используя Application.Forms, чтобы получить основную форму, и вызывает Invoke для этого), и дважды проверило в отладчике.
  • В связи с вышесказанным, существует некоторое обсуждение относительно использования Invoke против BeginInvoke для блокировки / отсутствия блокировки.В моем случае оба имеют одинаковый результат.
  • Некоторые сообщения предполагают, что необходимо создавать сами сокеты в потоке графического интерфейса (мои есть).
  • Этот объясняет, что вы можете получить ошибку, если вы используете DoEvents в своем приложении (я не знаю).
  • Этот также подразумевает, что вы могли получить ошибку с отсутствующим вызовом EndConnect при использовании асинхронных вызовов для соединения с сокетом клиента (мое соединение с клиентом является синхронным).
  • Этот объясняет, что вы можете получить неверные результаты из InvokeRequired, если дескриптор окна еще не создан (проверил это с помощью IsHandleCreated).
  • Этот в Microsoft Connect сообщает о похожей ошибке, но не имеет решения (Microsoft исследует ее с 2006 года!)
  • Этот содержит предложениеиспользовать AsyncOperationManager.SynchronizationContext для резервного копирования / восстановления контекста синхронизации, что (неудивительно?) просто вызывает различные ошибки.
  • Есть несколько постов, которые предполагают, что ошибка является только отладочной, и следующее заставит ее исчезнуть - но я не потрудился попробовать это:
    System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false

Есть и другие посты, задающие подобные вопросы: здесь , здесь и здесь .Хороший здесь тоже.

Вот фрагмент кода - это вызывает сбой в 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

I 'Я использую VB .NET 2010 с .NET4.

Спасибо за любую помощь - надеюсь, что приведенный выше список постов также поможет другим.

Тим

Стек вызовов:

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

Ответы [ 2 ]

2 голосов
/ 13 апреля 2012

Это исключение возникает при изменении свойства ExecutionContext потока. В частности, когда этот поток является потоком завершения или потоком завершения ввода-вывода, который выполняет обратный вызов, и он получил свой ExecutionContext от другого потока, который сделал вызов BeginXxx, чтобы запустить асинхронную операцию. Например, Socket.BeginReceive ().

Существует достаточно возможностей для того, чтобы это произошло в размещенном коде, так как он работает с формами в обратном вызове. ExecutionContext имеет скрытое свойство с именем SynchronizationContext, которое отслеживает SynchronizationContext.Current. Winforms устанавливает пользовательский поставщик синхронизации при первом создании любой формы. Требуется для правильного маршалинга вызовов из рабочего потока в поток пользовательского интерфейса. Это класс, производный от SynchronizationContext с именем WindowsFormsSynchronizationContext.

Таким образом, вероятный режим сбоя заключается в том, что метод sock_Receive () вызывается до создания любых форм Winforms. С помощью кода создания формы устанавливается поставщик синхронизации и изменяется ExecutionContext и, таким образом, происходит сбой кода с исключением. Такая проблема должна быть исправлена ​​путем изменения инициализации приложения, гарантирующего существование основной формы, прежде чем вы позволите любому асинхронному коду использовать BeginInvoke ().

0 голосов
/ 13 апреля 2012

Можете ли вы показать код, где вы вводите и заканчиваете ввод-вывод?

Вот возможный обходной путь: Давайте установим для synchronizationcontext.current значение null, когда вы начинаете все операции ввода-вывода.Похоже, что-то в .NET Framework запутывается и пытается дважды восстановить контекст выполнения.

Вот несколько полезных помощников:

    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 и восстановит его позже, что мы и видим в трассировке стека.

...