106

我正在使用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我在调用FromCurrentSynchronizationContextas 时收到错误消息:

当前的 SynchronizationContext 不能用作 TaskScheduler。

我想这是因为测试是在工作线程上运行的。如何确保测试在主线程上运行?欢迎任何其他建议。

4

3 回答 3

154

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

[SetUp]
public void TestSetUp()
{
  SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
于 2011-11-23T16:55:25.923 回答
30

Ritch Melton 的解决方案对我不起作用。这是因为我的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()并没有真正为该目标做得更好。

于 2016-04-09T21:59:50.173 回答
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 => { ... });
}
于 2017-09-08T14:54:22.260 回答