2011-11-23 585 views
87

我正在使用Tasks在我的ViewModel中运行长时间运行的服务器调用,并且使用TaskScheduler.FromSyncronizationContext()将结果汇编回Dispatcher。例如:当前SynchronizationContext不能用作TaskScheduler

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

这适用于我执行应用程序。但是,当我在Resharper运行我的NUnit测试中,我得到呼叫的错误消息FromCurrentSynchronizationContext为:

目前的SynchronizationContext可能不被用作的TaskScheduler。

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

回答

133

您需要提供一个SynchronizationContext。这是我如何处理它:

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

的工作,谢谢! – anivas

+5

对于MSTest:将上面的代码放在用ClassInitializeAttribute标记的方法中。 – SACO

+0

不知道... –

11

里奇梅尔顿的解决方案没有为我工作。这是因为我的TestInitialize功能与我的测试一样是异步的,所以每当await时,当前的SynchronizationContext都会丢失。这是因为如MSDN指出的那样,SynchronizationContext类是“哑巴”,只是将所有工作排队到线程池。

什么工作对我来说其实是刚刚跳过的FromCurrentSynchronizationContext呼叫时没有一个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到视图模型(通过依赖注入)
  • 创建测试SynchronizationContext和一个“假” UI螺纹测试运行 - 对我来说更麻烦,这是值得的

我失去了一些线程的细微差别,但我不是明确地测试我的OnPropertyChanged回调在特定线程上触发,所以我没关系。无论如何,使用new SynchronizationContext()的其他答案实际上并没有为这个目标做得更好。

+0

您的'else'情况也会在Windows服务应用程序中失败,导致'syncContextScheduler == null' – FindOutIslamNow

0

我已经联合多个解决方案,有担保工作的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 => { ... }); 
} 
相关问题