Как SynchronizationContext.Current основного потока может стать нулевым в приложении Windows Forms? - PullRequest
40 голосов
/ 11 января 2011

У меня проблема в приложении: в какой-то момент 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:

@ Ганс попросил найти трассировку стека. Вот оно:


   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

Ответы [ 3 ]

43 голосов
/ 09 мая 2012

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

9 голосов
/ 11 января 2011

Не уверен, что это предпочтительный метод, но вот как я использую SynchronizationContext:

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

_uiCtx = SynchronizationContext.Current;

И позже в своей Задаче используйте его для взаимодействия с основным потоком пользовательского интерфейса

_uiCtx.Post( ( o ) =>
{
 //UI Stuff goes here
}, null );
7 голосов
/ 13 сентября 2013

Я создал класс для этого. Это выглядит так:

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