当前的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);
这在我执行应用程序时正常工作。 但是当我在Resharper
上运行我的NUnit
testing时,我得到了FromCurrentSynchronizationContext
调用的错误消息:
当前的SynchronizationContext不能用作TaskScheduler。
我想这是因为testing工作线程上运行。 我怎样才能确保testing在主线程上运行? 欢迎任何其他build议。
你需要提供一个SynchronizationContext。 这是我如何处理它:
[SetUp] public void TestSetUp() { SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); }
里奇·梅尔顿的解决scheme并不适合我。 这是因为我的TestInitialize
函数和我的testing一样是asynchronous的,所以每当await
当前的SynchronizationContext
丢失。 这是因为如MSDN指出的那样, SynchronizationContext
类是“哑巴”,只是将所有的工作排队到线程池。
什么对我来说实际上只是在没有SynchronizationContext
(也就是说,如果当前的上下文为空 ),跳过FromCurrentSynchronizationContext
调用。 如果没有UI线程,我不需要首先与它同步。
TaskScheduler syncContextScheduler; if (SynchronizationContext.Current != null) { syncContextScheduler = TaskScheduler.FromCurrentSynchronizationContext(); } else { // If there is no SyncContext for this thread (eg 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; }
我发现这个解决scheme比替代scheme更直接,其中:
- 将一个
TaskScheduler
传递给ViewModel(通过dependency injection) - 创build一个testing
SynchronizationContext
和一个“假”UI线程的testing运行 – 对我来说更麻烦,这是值得的
我失去了一些线程的细微差别,但我没有明确地testing我的OnPropertyChangedcallback在特定的线程触发,所以我没关系。 使用new SynchronizationContext()
的其他答案实际上并没有为这个目标做更好的事情。
我已经结合了多个解决scheme来保证工作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 => { ... }); }