Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The current SynchronizationContext may not be used as a TaskScheduler

I am using Tasks to run long running server calls in my ViewModel and the results are marshalled back on Dispatcher using TaskScheduler.FromSyncronizationContext(). For example:

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

This works fine when I execute the application. But when I run my NUnit tests on Resharper I get the error message on the call to FromCurrentSynchronizationContext as:

The current SynchronizationContext may not be used as a TaskScheduler.

I guess this is because the tests are run on worker threads. How can I ensure the tests are run on main thread ? Any other suggestions are welcome.

like image 229
anivas Avatar asked Nov 23 '11 16:11

anivas


2 Answers

You need to provide a SynchronizationContext. This is how I handle it:

[SetUp] public void TestSetUp() {   SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); } 
like image 77
Ritch Melton Avatar answered Oct 09 '22 18:10

Ritch Melton


Ritch Melton's solution did not work for me. This is because my TestInitialize function is async, as are my tests, so with every await the current SynchronizationContext is lost. This is because as MSDN points out, the SynchronizationContext class is "dumb" and just queues all work to the thread pool.

What worked for me is actually just skipping over the FromCurrentSynchronizationContext call when there isn't a SynchronizationContext (that is, if the current context is null). If there's no UI thread, I don't need to synchronize with it in the first place.

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; } 

I found this solution more straightforward than the alternatives, which where:

  • Pass a TaskScheduler to the ViewModel (via dependency injection)
  • Create a test SynchronizationContext and a "fake" UI thread for the tests to run on - way more trouble for me that it's worth

I lose some of the threading nuance, but I am not explicitly testing that my OnPropertyChanged callbacks trigger on a specific thread so I am okay with that. The other answers using new SynchronizationContext() don't really do any better for that goal anyway.

like image 21
Sapph Avatar answered Oct 09 '22 17:10

Sapph