1

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)

EDIT: thanks to the help of @Theraot

    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?

Thanks a lot Christophe

chrisdot
  • 659
  • 6
  • 19
  • Your ProcessingQueue class already exists in the framework, it is ThreadPool. Hard to replace correctly, and it doesn't do anything that ThreadPool doesn't do. Other than the await and that promptly went wrong. Don't do it. – Hans Passant Oct 10 '17 at 09:23
  • @HansPassant what OP wants is different from the ThreadPool in that OP wants the added tasks to complete sequentially. On the ThreadPool you don't have such guarantee, in fact the idea of the ThreadPool is that is uses multiple Threads to run things in parallel (edit: and reuse the threads, of course). Now, that can be acomplished by running these tasks synchonously, or by other means that use a Thread for waiting... OP doesn't want any of that either. – Theraot Oct 10 '17 at 09:30
  • The Task class composits very well, all he need is ContinueWith to sequence the main task and WaitAll to wait for the sub-task completions. Easy peasy, but that just gets hard to see when you invent your own threadpool. – Hans Passant Oct 10 '17 at 09:35

1 Answers1

2

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,

Akash Kava
  • 39,066
  • 20
  • 121
  • 167