web-dev-qa-db-ja.com

WindowsフォームアプリケーションでメインスレッドのSynchronizationContext.Currentをnullにするにはどうすればよいですか?

アプリケーションに問題があります。ある時点で、メインスレッドのSynchronizationContext.Currentがnullになります。孤立したプロジェクトで同じ問題を再現できません。私の実際のプロジェクトは複雑です。 WindowsフォームとWPFを混合し、WCF Webサービスを呼び出します。私の知る限り、これらは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がnullになる原因は何ですか?

編集:

@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コールバック、オブジェクト状態、ブール値ignoreSyncCtx)
 at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task&cu rrentTaskSlot)
 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、Binderinder、Object [] parameters、CultureInfoculture、Boolean skipVisibilityChecks)
 at System.Delegate.DynamicInvokeImpl( Object。] args)
 at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
 at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Obje ct 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.ControlNative Window.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)
 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 
 MyApp.Framework.SharedUI.ApplicationBase.Start()のd:\ sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs:line 118 
 MyApp.App1.WinUI.HDA.Main()のd:\ sources\s2\App1\Sources\WinUI\HDA.cs:line 63 
 
40
Sylvain

Slyは、WPF、WCF、およびTPLの混合が使用されている場合、まったく同じ動作に遭遇しました。メインスレッドの現在のSynchronizationContextは、いくつかの状況でnullになります。

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フォーラムの この投稿 によると、これは4.0のTPLで確認されたバグです。同僚は4.5で実行しており、この動作は見られません。

FromCurrentSynchronizationContextを使用してメインスレッドで静的シングルトンにTaskSchedulerを作成し、継続を作成するときに常にそのタスクスケジューラを参照することで、これを解決しました。例えば

Task task = Task.Factory.StartNew(() =>
  {
    // something
  }
).ContinueWith(t =>
  {
    // ui stuff
  }, TheSingleton.Current.UiTaskScheduler);

これにより、.net 4.0のTPLの問題が回避されます。

Update開発マシンに.net 4.5がインストールされている場合、4.0フレームワークをターゲットにしている場合でも、この問題は発生しません。 4.0のみがインストールされているユーザーも影響を受けます。

43
Dan

これが推奨される方法かどうかはわかりませんが、ここにSynchronizationContextの使用方法があります。

コンストラクター(メインスレッド)で現在のコンテキストのコピーを保存します。これにより、どのスレッドを使用していても、後で正しいコンテキストが保証されます(??)。

_uiCtx = SynchronizationContext.Current;

そして、後でタスクでそれを使用してメインUIスレッドと対話します

_uiCtx.Post( ( o ) =>
{
 //UI Stuff goes here
}, null );
10
kbeal2k

このためのクラスを作成しました。次のようになります。

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