A continuation of the task was scheduled for the thread pool thread. What for?

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

+2
source share
1 answer

I think you found a bug in Mono runtime. Continuation after await should not occur in a thread with a different synchronization context from what TaskAwaiter , at the await point.

The following scenarios are possible:

  • Both the source thread and the termination thread have the same synchronization context. Continuation can be enabled (performed synchronously in the completion thread).
  • Both the source thread and the termination thread do not have a synchronization context ( SynchronizationContext.Current == null ). Continuation may still be included.
  • In any other combination, the continuation should not be included.

By "maybe inlined" I mean that it is not required or guaranteed (this can be scheduled using TaskScheduler.Current or TaskScheduler.FromCurrentSynchronizationContext for asynchronous execution). However, in accordance with the current Microsoft TPL implementation, it is embedded in conditions # 1 and # 2.

However, for # 3 it should not be built in , it is dictated by common sense. Therefore, do not hesitate to report an error to Xamarin. First try the latest Mono build to see if there is a problem.

0
source

Source: https://habr.com/ru/post/1206931/


All Articles