In my console application, I create my own thread to implement the work queue. In addition, I applied my own SynchronizationContext for this thread only.
When I wait for Task from the main thread, a sudden continuation (the rest of my routine) is scheduled for my work thread, which is not the case because I do not expect my thread to be used as a ThreadPool thread for random tasks.
I only observe this behavior when running code using Mono.
Here is the code that reproduces the problem on mono (tested on mac os x and linux system):
using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; class Program { static void Main( string[] args ) { Foo(); Console.ReadLine(); } async static void Foo() { Console.WriteLine( "{0}: current thread ID={1}; scheduler={2}; context={3};", " Main BEFORE awaiting", Thread.CurrentThread.ManagedThreadId, TaskScheduler.Current.Id, SynchronizationContext.Current != null ); // MONO Output: Main BEFORE awaiting: current thread ID=1; scheduler=1; context=False; WorkQueue queue = new WorkQueue(); // !!! // I do expect that current context which is null will be captured for continuation. // !!! await queue.Enqueue(); // !!! // As we can see our custom context was captured to continue with this part of code. // Console.WriteLine( "{0}: current thread ID={1}; scheduler={2}; context={3};", " Main AFTER awaiting", Thread.CurrentThread.ManagedThreadId, TaskScheduler.Current.Id, SynchronizationContext.Current != null ); // MONO Output: Main AFTER awaiting: current thread ID=4; scheduler=1; context=True; } } // Custom context which does nothing but enqueues fake tasks to the queue. // class WorkQueueSyncContext : SynchronizationContext { readonly WorkQueue queue; public WorkQueueSyncContext( WorkQueue queue ) { this.queue = queue; } public override void Post( SendOrPostCallback d, object state ) { } public override void Send( SendOrPostCallback d, object state ) { queue.Enqueue().Wait(); } } // The queue // class WorkQueue { readonly Thread thread; class WorkQueueItem { public TaskCompletionSource<object> Completion { get; set; } } BlockingCollection<WorkQueueItem> queue = new BlockingCollection<WorkQueueItem>(); public WorkQueue() { thread = new Thread( new ThreadStart( Run ) ); thread.Start(); } private void Run() { // Set ower own SynchronizationContext. // SynchronizationContext.SetSynchronizationContext( new WorkQueueSyncContext( this ) ); Console.WriteLine( "{0}: current thread ID={1}; scheduler={2}; context={3};", " WorkQueue START", Thread.CurrentThread.ManagedThreadId, TaskScheduler.Current.Id, SynchronizationContext.Current != null ); // MONO Output: current thread ID=4; scheduler=1; context=True; // Working loop. // while ( true ) { WorkQueueItem item = queue.Take(); Console.WriteLine( "{0}: current thread ID={1}; scheduler={2}; context={3};", " WorkQueue DOING TASK", Thread.CurrentThread.ManagedThreadId, TaskScheduler.Current.Id, SynchronizationContext.Current != null ); // MONO Output: current thread ID=4; scheduler=1; context=True; // Completed the task :) // item.Completion.SetResult( true ); } } public Task<object> Enqueue() { TaskCompletionSource<object> completion = new TaskCompletionSource<object>(); queue.Add( new WorkQueueItem() { Completion = completion } ); return completion.Task; } }
So here is the MONO output:
Main BEFORE awaiting: current thread ID=1; scheduler=1; context=False; WorkQueue START: current thread ID=3; scheduler=1; context=True; WorkQueue DOING TASK: current thread ID=3; scheduler=1; context=True; Main AFTER awaiting: current thread ID=3; scheduler=1; context=True;
And this is the output of Windows:
Main BEFORE awaiting: current thread ID=10; scheduler=1; context=False; WorkQueue START: current thread ID=11; scheduler=1; context=True; WorkQueue DOING TASK: current thread ID=11; scheduler=1; context=True; Main AFTER awaiting: current thread ID=6; scheduler=1; context=False;
Note (last line) how context transfer is different.
EDIT:
It does not play with Mono 3.4.0, so it seems to be a bug in the old version (at least 3.2.6);