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 deMyContinueWith
. - 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:
- ¿Cómo puede SynchronizationContext.Current del hilo principal convertirse en nulo en una aplicación de Windows Forms?
- http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/629d5524-c8db-466f-bc27-0ced11b441ba
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.