c# winforms task-parallel-library synchronizationcontext

c# - SynchronizationContext.Current es nulo en Continuación en el hilo principal de la interfaz de usuario



winforms task-parallel-library (1)

He estado tratando de rastrear el siguiente problema en una aplicación de Winforms:
SynchronizationContext.Current es nulo en la continuación de una tarea (es decir, .ContinueWith ) que se ejecuta en el hilo principal (espero que el contexto de sincronización actual sea System.Windows.Forms.WindowsFormsSynchronizationContext ).

Aquí está el código de Winforms que demuestra el problema:

using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); TaskScheduler ts = TaskScheduler.FromCurrentSynchronizationContext(); // Get the UI task scheduler // This line is required to see the issue (Removing this causes the problem to go away), since it changes the codeflow in // /SymbolCache/src/source/.NET/4/DEVDIV_TFS/Dev10/Releases/RTMRel/ndp/clr/src/BCL/System/Threading/ExecutionContext.cs/1305376/ExecutionContext.cs // at line 435 System.Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation"); var task = Task.Factory.StartNew(() => { }); var cont = task.ContinueWith(MyContinueWith, CancellationToken.None, TaskContinuationOptions.None, ts); System.Diagnostics.Trace.CorrelationManager.StopLogicalOperation(); } void MyContinueWith(Task t) { if (SynchronizationContext.Current == null) // The current SynchronizationContext shouldn''t be null here, but it is. MessageBox.Show("SynchronizationContext.Current is null"); } } }

Este es un problema para mí ya que intento utilizar BackgroundWorker desde la continuación, y BackgroundWorker utilizará el SynchronizationContext actual para sus eventos RunWorkerCompleted y ProgressChanged . Como el SynchronizationContext actual es nulo cuando inicio el BackgroundWorker, los eventos no se ejecutan en el hilo ui principal como pretendo.

Mi pregunta:
¿Es esto un error en el código de Microsoft, o he cometido un error en alguna parte?

Información adicional:

  • Estoy usando .Net 4.0 (todavía no lo he probado en .NET 4.5 RC)
  • Puedo reproducir esto en Debug / Release en cualquiera de las CPU x86 / x64 / Any (en una máquina x64).
  • Se reproduce constantemente (me interesaría si alguien no puede reproducirlo).
  • Tengo un código heredado que usa BackgroundWorker, así que no puedo cambiar fácilmente para no usar BackgroundWorker
  • Confirmé que el código en MyContinueWith está ejecutando en el hilo principal de la interfaz de MyContinueWith .
  • No sé exactamente por qué la llamada StartLogicalOperation ayuda a causar el problema, eso es precisamente lo que he reducido a mi aplicación.

El problema está solucionado en .NET 4.5 RC (recién probado). Así que supongo que es un error en .NET 4.0. Además, supongo que estas publicaciones hacen referencia al mismo problema:

Eso es lamentable. Ahora tengo que considerar soluciones temporales.

Editar:
Desde la depuración en la fuente .Net, tengo un poco mejor entendimiento de cuándo se reproducirá el problema. Aquí hay un código relevante de ExecutionContext.cs:

internal static void Run(ExecutionContext executionContext, ContextCallback callback, Object state, bool ignoreSyncCtx) { // ... Some code excluded here ... ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate(); if ( (ec == null || ec.IsDefaultFTContext(ignoreSyncCtx)) && #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK SecurityContext.CurrentlyInDefaultFTSecurityContext(ec) && #endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK executionContext.IsDefaultFTContext(ignoreSyncCtx)) { callback(state); } else { if (executionContext == s_dummyDefaultEC) executionContext = s_dummyDefaultEC.CreateCopy(); RunInternal(executionContext, callback, state); } }

El problema solo se reproduce cuando entramos en la cláusula "else" que llama a RunInternal. Esto se debe a que RunInternal termina reemplazando el ExecutionContext que tiene el efecto de cambiar el actual SynchronizationContext:

// Get the current SynchronizationContext on the current thread public static SynchronizationContext Current { get { SynchronizationContext context = null; ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate(); if (ec != null) { context = ec.SynchronizationContext; } // ... Some code excluded ... return context; } }

Entonces, para mi caso específico, fue porque la línea `executionContext.IsDefaultFTContext (ignoreSyncCtx)) devolvió false. Aquí está ese código:

internal bool IsDefaultFTContext(bool ignoreSyncCtx) { #if FEATURE_CAS_POLICY if (_hostExecutionContext != null) return false; #endif // FEATURE_CAS_POLICY #if FEATURE_SYNCHRONIZATIONCONTEXT if (!ignoreSyncCtx && _syncContext != null) return false; #endif // #if FEATURE_SYNCHRONIZATIONCONTEXT #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK if (_securityContext != null && !_securityContext.IsDefaultFTSecurityContext()) return false; #endif //#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK if (_logicalCallContext != null && _logicalCallContext.HasInfo) return false; if (_illogicalCallContext != null && _illogicalCallContext.HasUserData) return false; return true; }

Para mí, eso devolvía falso debido a _logicalCallContext.HasInfo era verdadero. Aquí está ese código:

public bool HasInfo { [System.Security.SecurityCritical] // auto-generated get { bool fInfo = false; // Set the flag to true if there is either remoting data, or // security data or user data if( (m_RemotingData != null && m_RemotingData.HasInfo) || (m_SecurityData != null && m_SecurityData.HasInfo) || (m_HostContext != null) || HasUserData ) { fInfo = true; } return fInfo; } }

Para mí, esto estaba volviendo verdadero porque HasUserData era verdadero. Aquí está ese código:

internal bool HasUserData { get { return ((m_Datastore != null) && (m_Datastore.Count > 0));} }

Para mí, m_DataStore tendría elementos debido a mi llamada a Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation");

En resumen, parece que hay varias formas diferentes de reproducir el error. Afortunadamente, este ejemplo servirá para ayudar a otros a determinar si se están encontrando con este mismo error o no.