当前的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()在lambda内部使用,执行被推迟到另一个线程。在lambda之外获取上下文可解决此问题。
M.kazem Akhgary

Answers:


145

您需要提供一个SynchronizationContext。这是我的处理方式:

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

6
对于MSTest:将上面的代码放在标有ClassInitializeAttribute的Method中。
DanielBişar2013年

6
@SACO:实际上,我必须将它放在带有的方法中TestInitializeAttribute,否则只有第一个测试通过。
Thorarin

2
对于xunit测试,我将其放在静态类型ctor中,因为每个夹具只需要设置一次即可。
codekaizen 2014年

3
我根本不明白为什么这个答案被接受为解决方案。这是行不通的。原因很简单:SynchronizationContext是一个伪类,其发送/发布功能无用。该类应该是抽象的,而不是具体的类,它可能使人们误以为“它正在起作用”。@tofutim您可能想提供从SyncContext派生的自己的实现。
h9uest

1
我想我知道了。我的TestInitialize是异步的。每当TestInit中有一个“ await”时,当前的SynchronizationContext都会丢失。这是因为(如@ h9uest所指出的),SynchronizationContext的默认实现只是将任务排队到ThreadPool中,实际上并没有在同一线程上继续执行。
萨普,2016年

24

里奇·梅尔顿(Ritch Melton)的解决方案对我不起作用。这是因为我的TestInitialize功能和测试一样是异步的,因此每一次await电流SynchronizationContext都会丢失。这是因为正如MSDN所指出的,SynchronizationContext该类是“哑”类,只是将所有工作排队到线程池中。

实际上,对我有用的只是在没有FromCurrentSynchronizationContext呼叫时跳过呼叫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;
}

我发现此解决方案比其他解决方案更直接,其中:

  • 传递TaskScheduler给ViewModel(通过依赖注入)
  • 创建一个测试SynchronizationContext和一个“假” UI线程以运行测试-对我来说,麻烦更多的是值得

我失去了一些线程上的细微差别,但是我没有明确测试我的OnPropertyChanged回调是否在特定线程上触发,因此我可以接受。new SynchronizationContext()无论如何,使用其他答案并不能真正实现该目标。


您的else案件也将在Windows Service应用程序中失败,结果syncContextScheduler == null
FindOutIslamNow

遇到了同样的问题,但是我读了NUnit源代码。如果AsyncToSyncAdapter在STA线程中运行,则它仅覆盖SynchronizationContext。一种解决方法是使用[RequiresThread]属性标记您的班级。
阿隆(Aron)

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 => { ... });
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.