Как SynchronizationContext.Current основного потока становится нулевым в приложении Windows Forms?
У меня проблема в моем приложении: в какой-то момент SynchronizationContext.Current становится нулевым для основного потока. Я не могу воспроизвести ту же проблему в изолированном проекте. Мой настоящий проект сложный; он смешивает Windows Forms и WPF и вызывает веб-службы WCF. Насколько я знаю, это все системы, которые могут взаимодействовать с SynchronizationContext.
Это код из моего изолированного проекта. Мое настоящее приложение делает что-то похожее на это. Однако в моем реальном приложении SynchronizationContext.Current имеет значение null в основном потоке, когда выполняется задача продолжения.
private void button2_Click(object sender, EventArgs e)
{
if (SynchronizationContext.Current == null)
{
Debug.Fail("SynchronizationContext.Current is null");
}
Task.Factory.StartNew(() =>
{
CallWCFWebServiceThatThrowsAnException();
})
.ContinueWith((t) =>
{
//update the UI
UpdateGUI(t.Exception);
if (SynchronizationContext.Current == null)
{
Debug.Fail("SynchronizationContext.Current is null");
}
}, CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext());
}
Что может привести к тому, что SynchronizationContext.Current основного потока станет нулевым?
Edit:
@Hans запросил трассировку стека. Вот он:
at MyApp.Framework.UI.Commands.AsyncCommand.HandleTaskError(Task task) in d:\sources\s2\Framework\Sources\UI\Commands\AsyncCommand.cs:line 157
at System.Threading.Tasks.Task.c__DisplayClassb.b__a(Object obj)
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
at System.Threading.Tasks.Task.ExecutionContextCallback(Object obj)
at System.Threading.ExecutionContext.runTryCode(Object userData)
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.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
at System.Threading.Tasks.SynchronizationContextTaskScheduler.PostCallback(Object obj)
at System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
at System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj)
at System.Threading.ExecutionContext.runTryCode(Object userData)
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.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme)
at System.Windows.Forms.Control.InvokeMarshaledCallbacks()
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at MyApp.Framework.SharedUI.ApplicationBase.InternalStart() in d:\sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs:line 190
at MyApp.Framework.SharedUI.ApplicationBase.Start() in d:\sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs:line 118
at MyApp.App1.WinUI.HDA.Main() in d:\sources\s2\App1\Sources\WinUI\HDA.cs:line 63
Ответы
Ответ 1
Sly, я столкнулся с тем же самым поведением, когда используется смесь WPF, WCF и TPL. Текущий текущий поток SynchronizationContext станет нулевым в нескольких ситуациях.
var context = SynchronizationContext.Current;
// if context is null, an exception of
// The current SynchronizationContext may not be used as a TaskScheduler.
// will be thrown
TaskScheduler.FromCurrentSynchronizationContext();
В соответствии с этим сообщением на форумах msdn это подтвержденная ошибка в TPL в 4.0. Сотрудник работает на 4.5 и не видит этого поведения.
Мы решили это, создав TaskScheduler в статическом singleton с основным потоком, используя FromCurrentSynchronizationContext, а затем всегда ссылайтесь на этот планировщик задач при создании продолжений. Например
Task task = Task.Factory.StartNew(() =>
{
// something
}
).ContinueWith(t =>
{
// ui stuff
}, TheSingleton.Current.UiTaskScheduler);
Это позволяет избежать проблемы в TPL на .net 4.0.
Обновление
Если на компьютере разработчика установлена программа .net 4.5, вы не увидите эту проблему, даже если вы настроили таргетинг на фреймворк 4.0. Ваши пользователи, у которых установлен только 4.0, по-прежнему будут затронуты.
Ответ 2
Не уверен, что это предпочтительный метод, но вот как я использую SynchronizationContext:
В вашем конструкторе (основной поток) сохраняйте копию текущего контекста, таким образом вы гарантируете (??), чтобы иметь правильный контекст позже независимо от того, в какой поток вы находитесь.
_uiCtx = SynchronizationContext.Current;
И позже в вашей Задаче используйте это, чтобы взаимодействовать с основным потоком пользовательского интерфейса
_uiCtx.Post( ( o ) =>
{
//UI Stuff goes here
}, null );
Ответ 3
Я создал для этого класс. Это выглядит так:
public class UIContext
{
private static TaskScheduler m_Current;
public static TaskScheduler Current
{
get { return m_Current; }
private set { m_Current = value; }
}
public static void Initialize()
{
if (Current != null)
return;
if (SynchronizationContext.Current == null)
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
Current = TaskScheduler.FromCurrentSynchronizationContext();
}
}
При запуске моего приложения я вызываю UIContext.Initialize()
И когда мне это нужно в задаче, я просто кладу UIContext.Current как TaskScheduler.
Task.Factory.StartNew(() =>
{
//Your code here
}, CancellationToken.None, TaskCreationOptions.None, UIContext.Current);