Thread Join () causes Task.RunSynchronously not to terminate

Calling _thread.Join() closes the GetConsumingEnumerable loop on the last element. Why is this behavior happening?

  public abstract class ActorBase : IDisposable { private readonly BlockingCollection<Task> _queue = new BlockingCollection<Task>(new ConcurrentQueue<Task>()); private readonly Thread _thread; private bool _isDisposed; protected ActorBase() { _thread = new Thread(ProcessMessages); _thread.Start(); } protected void QueueTask(Task task) { if (_isDisposed) { throw new Exception("Actor was disposed, cannot queue task."); } _queue.Add(task); } private void ProcessMessages() { foreach (var task in _queue.GetConsumingEnumerable()) { task.RunSynchronously(); } } public void Dispose() { _isDisposed = true; _queue.CompleteAdding(); _thread.Join(); } } public class SampleActor : ActorBase { private string GetThreadStatus() { Thread.Sleep(500); return string.Format("Running on thread {0}", Thread.CurrentThread.ManagedThreadId); } public async Task<string> GetThreadStatusAsync() { var task = new Task<string>(GetThreadStatus); QueueTask(task); return await task; } } class Program { public static async Task Run() { using (var sa = new SampleActor()) { for (int i = 0; i < 3; i++) { Console.WriteLine(await sa.GetThreadStatusAsync()); } } } public static void Main(string[] args) { Console.WriteLine("Main thread id {0}", Thread.CurrentThread.ManagedThreadId); var task = Task.Run(async ()=> { await Run(); }); task.Wait(); } } 

The context of this approach is that I need to make sure that all operations are performed in the same OS thread, which will allow part of the application to use different credentials than the main thread.

+8
multithreading c # threadpool task-parallel-library async-await
source share
2 answers

async-await works with sequel. To be effective and reduce planning, these continuations are usually executed in the same thread that completed the previous task.

This means that your special thread not only performs tasks, but also performs all the continuations after these tasks (the for loop itself). This can be seen by typing the stream identifier:

 using (var sa = new SampleActor()) { for (int i = 0; i < 3; i++) { Console.WriteLine(await sa.GetThreadStatusAsync()); Console.WriteLine("Continue on thread :" + Thread.CurrentThread.ManagedThreadId); } } 

When the for loop completes and the SampleActor is located, you call Thread.Join from the same thread that you are trying to join so you get stuck. Your situation is as follows:

 public static void Main() { Thread thread = null; thread = new Thread(() => { Thread.Sleep(100); thread.Join(); Console.WriteLine("joined"); }); thread.Start(); } 

In .Net 4.6 you can solve this with TaskCreationOptions.RunContinuationsAsynchronously , but in the current version you can specify the default value of TaskScheduler :

 public Task<string> GetThreadStatusAsync() { var task = new Task<string>(GetThreadStatus); QueueTask(task); return task.ContinueWith(task1 => task1.GetAwaiter().GetResult(), TaskScheduler.Default); } 
+5
source share

You might be tempted to put a simple check to see if the thread you are trying to Thread.CurrentThread , but that would be wrong.

In addition, I think that the whole approach - scheduling and running cold Task objects with a custom scheduler that is not compatible with TPL - is wrong . You should use a TPL-friendly task scheduler similar to Stephen Toub StaTaskScheduler . Or, run a custom SynchronizationContext for your current thread (e.g. Toub AsyncPump ) and use TaskScheduler.FromCurrentSynchronizationContext and Task.Factory.StartNew to schedule a task with your custom scheduler (or use Task.Start(TaskScheduler) if you have to solve cold problems).

Thus, you will have full control over where the tasks are performed and their continuation, as well as the task of investing .

+4
source share

All Articles