web-dev-qa-db-ja.com

現在のSynchronizationContextはTaskSchedulerとして使用できません

私は Tasks を使用してViewModelで長時間実行されているサーバー呼び出しを実行し、TaskScheduler.FromSyncronizationContext()を使用してDispatcherで結果をマーシャリングします。例えば:

var context = TaskScheduler.FromCurrentSynchronizationContext();
this.Message = "Loading...";
Task task = Task.Factory.StartNew(() => { ... })
            .ContinueWith(x => this.Message = "Completed"
                          , context);

これは、アプリケーションを実行すると正常に機能します。しかし、NUnitResharperテストを実行すると、FromCurrentSynchronizationContextの呼び出しでエラーメッセージが表示されます。

現在のSynchronizationContextをTaskSchedulerとして使用することはできません。

これは、テストがワーカースレッドで実行されるためだと思います。テストがメインスレッドで実行されるようにするにはどうすればよいですか?他の提案は大歓迎です。

95
anivas

SynchronizationContextを提供する必要があります。これは私がそれを処理する方法です:

[SetUp]
public void TestSetUp()
{
  SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
139
Ritch Melton

Ritch Meltonのソリューションは私にはうまくいきませんでした。これは、私のTestInitialize関数がテストと同様に非同期であるため、すべてのawaitで現在のSynchronizationContextが失われるためです。これは、MSDNが指摘しているように、SynchronizationContextクラスが「ダム」であり、すべての作業をスレッドプールにキューイングするだけだからです。

私のために働いたのは、実際にFromCurrentSynchronizationContextがないとき(つまり、現在のコンテキストがnullの場合)SynchronizationContext呼び出しをスキップすることです。 UIスレッドがない場合、そもそもそれと同期する必要はありません。

_TaskScheduler syncContextScheduler;
if (SynchronizationContext.Current != null)
{
    syncContextScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
else
{
    // If there is no SyncContext for this thread (e.g. we are in a unit test
    // or console scenario instead of running in an app), then just use the
    // default scheduler because there is no UI thread to sync with.
    syncContextScheduler = TaskScheduler.Current;
}
_

このソリューションは、他の選択肢よりも簡単です。

  • TaskSchedulerをViewModelに渡します(依存性注入を介して)
  • テストを実行するためのテストSynchronizationContextと「偽の」UIスレッドを作成します-私にとってはもっと価値のあるトラブル

スレッドのニュアンスの一部は失われますが、特定のスレッドでOnPropertyChangedコールバックがトリガーされることを明示的にテストしているわけではないので、大丈夫です。 new SynchronizationContext()を使用する他の答えは、とにかくその目標に対して実際にはそれ以上良くなりません。

20
Sapph

SynchronizationContextの動作を保証するために、複数のソリューションを組み合わせました。

using System;
using System.Threading;
using System.Threading.Tasks;

public class CustomSynchronizationContext : SynchronizationContext
{
    public override void Post(SendOrPostCallback action, object state)
    {
        SendOrPostCallback actionWrap = (object state2) =>
        {
            SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext());
            action.Invoke(state2);
        };
        var callback = new WaitCallback(actionWrap.Invoke);
        ThreadPool.QueueUserWorkItem(callback, state);
    }
    public override SynchronizationContext CreateCopy()
    {
        return new CustomSynchronizationContext();
    }
    public override void Send(SendOrPostCallback d, object state)
    {
        base.Send(d, state);
    }
    public override void OperationStarted()
    {
        base.OperationStarted();
    }
    public override void OperationCompleted()
    {
        base.OperationCompleted();
    }

    public static TaskScheduler GetSynchronizationContext() {
      TaskScheduler taskScheduler = null;

      try
      {
        taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
      } catch {}

      if (taskScheduler == null) {
        try
        {
          taskScheduler = TaskScheduler.Current;
        } catch {}
      }

      if (taskScheduler == null) {
        try
        {
          var context = new CustomSynchronizationContext();
          SynchronizationContext.SetSynchronizationContext(context);
          taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
        } catch {}
      }

      return taskScheduler;
    }
}

使用法:

var context = CustomSynchronizationContext.GetSynchronizationContext();

if (context != null) 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... }, context);
}
else 
{
    Task.Factory
      .StartNew(() => { ... })
      .ContinueWith(x => { ... });
}
0
ujeenator