.netmultithreadingasynchronoustask

C# 6 waiting for full completion of task and all subtasks


I'm trying to build up a kind of scheduler (this might not be the relevant term) that would run in sequence a number of tasks.

Here is my POC code (please ignore the queue/dequeue mechanism which is poor, but not the problem here I guess)

    static void Main(string[] args)
    {
        ProcessingQueue o_q = new ProcessingQueue();
        o_q.Enqueue(async () => { await SimulateTaskSequence(1); });
        o_q.Enqueue(async () => { await SimulateTaskSequence(2); });

        Console.ReadLine();
    }

    public static async Task SimulateTaskSequence(int taskNbr)
    {
        Console.WriteLine("T{0} - Working 1sec", taskNbr);
        Thread.Sleep(1000);

        Console.WriteLine("T{0} - Zzz 1st 1sec", taskNbr);
        await Task.Delay(1000);

        Console.WriteLine("T{0} - Working 1sec", taskNbr);
        Thread.Sleep(1000);

        Console.WriteLine("T{0} - Done", taskNbr);
    }

    public class ProcessingQueue
    {
        Queue<Action> _Queue = new Queue<Action>();
        private bool _stillRunning = false;

        public void Enqueue(Action a)
        {
            lock (_Queue)
            {
                _Queue.Enqueue(a);

                if (_stillRunning == false)
                {
                    StartProcessing();
                }
            }
        }


        private void StartProcessing()
        {
            _stillRunning = true;

            Task.Run(async () =>
            {
                Action a = null;

                while (true)
                {
                    lock (_Queue)
                    {
                        if (_Queue.Any() == true)
                        {
                            a = _Queue.Dequeue();
                        }
                        else
                        {
                            break;
                        }
                    }

                    await Task.Run(a); //how to wait for all subtasks!!???
                }
                _stillRunning = false;
            });
        }

My problem is that as soon as the first await of the 1st task (T1) occurs, the second tasks (T2) starts to get executed.

I get the following output:

T1 - Working 1sec
T1 - Zzz 1st 1sec
T2 - Working 1sec
T2 - Zzz 1st 1sec
T1 - Working 1sec
T1 - Done
T2 - Working 1sec
T2 - Done

But what I'm expecting would be:

T1 - Working 1sec
T1 - Zzz 1st 1sec
T1 - Working 1sec
T1 - Done
T2 - Working 1sec
T2 - Zzz 1st 1sec
T2 - Working 1sec
T2 - Done

I understand why this is the default behavior, but I need to change that. I was playing around TaskContinuationOptions and TaskCreationOptions in a new TaskFactory, but without better results. Is that even possible?


Solution

  • I would suggest creating a ProcessingQueue<Func<Task>> instead of ProcessingQueue<Action>

    public class ProcessingQueue
    {
        Queue<Func<Task>> _Queue = new Queue<Func<Task>>();
    
        private bool _stillRunning = false;
    
        public void Enqueue(Func<Task> a)
        {
            lock (_Queue)
            {
                _Queue.Enqueue(a);
    
                if (_stillRunning == false)
                {
                    StartProcessing();
                }
            }
        }
    
        private void StartProcessing()
        {
            _stillRunning = true;
    
            Task.Run(async () =>
            {
                Func<Task> a = null;
    
                while (true)
                {
                    lock (_Queue)
                    {
                        if (_Queue.Any() == true)
                        {
                            a = _Queue.Dequeue();
                        }
                        else
                        {
                            break;
                        }
                    }
    
                    await a(); //how to wait for all subtasks!!???
                }
                _stillRunning = false;
            });
        }
    

    Explanation

    In code written in question,

    Action a;
    ...
    await Task.Run(a);
    

    You are executing Task.Run(Action action), since action might contain an asynchronous task, Run method does not await on task because there is no task. When you call Task.Run(Func<Task> task) Run method knows that it is task and it will await on it,