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


98

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

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

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

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

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


私の場合TaskScheduler.FromCurrentSynchronizationContext()、ラムダ内で使用していて、実行は別のスレッドに延期されました。ラムダの外でコンテキストを取得すると、問題が修正されました。
M.kazem Akhgary 2018

回答:


145

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

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

6
MSTestの場合:上記のコードをClassInitializeAttributeでマークされたメソッドに配置します。
DanielBişar13年

6
@SACO:実際、私はそれをを使用してメソッドに配置する必要TestInitializeAttributeがあります。そうしないと、最初のテストのみに合格します。
Thorarin 2013年

2
xunitテストの場合は、フィクスチャごとに1回だけ設定する必要があるため、静的型のctorに入れます。
codekaizen 2014

3
この答えがなぜ解決策として受け入れられたのか、私にはまったくわかりません。それは動作しません。その理由は簡単です。SynchronizationContextは、送信/送信機能が役に立たないダミークラスです。このクラスは、人々を「機能している」という誤った感覚に導く可能性がある具体的なクラスではなく、抽象的である必要があります。@tofutim SyncContextから派生した独自の実装を提供したい場合があります。
h9uest 2015年

1
私はそれを理解したと思います。私のTestInitializeは非同期です。TestInitに「待機」があるたびに、現在のSynchronizationContextが失われます。これは(@ h9uestが指摘したように)、SynchronizationContextの既定の実装では、タスクがThreadPoolにキューイングされるだけで、実際には同じスレッドで続行されないためです。
サフ

24

リッチ・メルトンの解決策は私にはうまくいきませんでした。これはTestInitialize、テストと同様に、私の機能が非同期であるため、すべてawaitの電流SynchronizationContextが失われるためです。これは、MSDNが指摘しているように、SynchronizationContextクラスは「ダム」であり、すべての作業をスレッドプールにキューイングするだけだからです。

私にとってうまくいったのは、実際FromCurrentSynchronizationContextにはaがないSynchronizationContext場合(つまり、現在のコンテキストがnullの場合)に呼び出しをスキップすることです。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;
}

私はこの解決策が代替案よりも簡単であることに気づきました。

  • a TaskSchedulerをViewModelに渡します(依存関係の注入を介して)
  • テストSynchronizationContextと、テストを実行するための「偽の」UIスレッドを作成します。

スレッドのニュアンスの一部を失いましたが、OnPropertyChangedコールバックが特定のスレッドでトリガーされることを明示的にテストしていないため、問題ありません。new SynchronizationContext()とにかく、他の答えを使用しても、実際にはその目標には効果がありません。


あなたのelse場合は、結果、Windowsサービスのアプリでも失敗しますsyncContextScheduler == null
FindOutIslamNow

同じ問題に遭遇しましたが、代わりにNUnitソースコードを読みました。AsyncToSyncAdapterは、STAスレッドで実行されている場合にのみSynchronizationContextをオーバーライドします。回避策は、クラスを[RequiresThread]属性でマークすることです。
アロン

1

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 => { ... });
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.