Ramsey
Ramsey

Reputation: 41

Async tasks executing in order?

My goal is to create a system that spawns an initial number of tasks(all the same). These tasks will grab and perform some background operation (from a DB) and then return, at which point a new task should spawn and do the same thing.

I have written the following code as a proof of concept, but it seems like all my tasks are executing 1 by 1, not in parallel.

Code:

public Form1()
    {
        InitializeComponent();
    }
    CancellationTokenSource cts;
    private async void button1_Click(object sender, EventArgs e)
    {   
        cts = new CancellationTokenSource();
        int reqNumberOfThreads = int.Parse(textBox1.Text);
        try
        {
            await startSlaves(cts.Token, reqNumberOfThreads);
        }
        catch (OperationCanceledException)
        {
            MessageBox.Show("Canceled Starting Threads");
        }
        cts = null;

    }
    async Task startSlaves(CancellationToken ct, int threadNum)
    {

        List<Task<int>> allTasks = new List<Task<int>>();// ***Add a loop to process the tasks one at a time until none remain. 
        for (int x = 0; x < threadNum; x++)
        {
            allTasks.Add(beginSlaveOperation(ct, x));
        }
        // ***Add a loop to process the tasks one at a time until none remain. 
        while (allTasks.Count <= threadNum)
        {
            // Identify the first task that completes.
            Task<int> output = await Task.WhenAny(allTasks);
            allTasks.Remove(output);
            allTasks.Add(beginSlaveOperation(ct, output.Result));
        }
    }
    public void performExampleImportOperationThread(int inputVal, int whoAmI)
    {
        System.Threading.Thread.Sleep(inputVal*10);
        System.Console.Write("Thread number" + whoAmI.ToString() + "has finished after "+inputVal.ToString()+" secs \n");
    }
    async Task<int> beginSlaveOperation(CancellationToken ct, int whoAmI)
    {
        Random random = new Random();
        int randomNumber = random.Next(0, 100);//Get command from microSched and remove it from sched
        performExampleImportOperationThread(randomNumber, whoAmI);//perform operation
        return whoAmI;
    }

    private void button2_Click(object sender, EventArgs e)
    {
        if (cts != null)
        {
            cts.Cancel();
        }
    }

Output:

Thread number0has finished after 29 secs 
Thread number1has finished after 45 secs 
Thread number2has finished after 59 secs 
Thread number0has finished after 39 secs 
Thread number1has finished after 13 secs 
Thread number2has finished after 44 secs 
Thread number0has finished after 21 secs 
Thread number1has finished after 62 secs 
Thread number2has finished after 62 secs 
Thread number0has finished after 25 secs 
Thread number1has finished after 86 secs 
Thread number2has finished after 10 secs 
Thread number0has finished after 4 secs 
Thread number1has finished after 24 secs 
Thread number2has finished after 84 secs 
Thread number0has finished after 73 secs 
Thread number1has finished after 19 secs 
Thread number2has finished after 72 secs 
Thread number0has finished after 82 secs 

Upvotes: 1

Views: 2480

Answers (2)

Ramsey
Ramsey

Reputation: 41

Here's the fixed code for future reference:

public Form1()
    {
        InitializeComponent();
    }
    CancellationTokenSource cts;
    private async void button1_Click(object sender, EventArgs e)
    {   
        cts = new CancellationTokenSource();
        int reqNumberOfThreads = int.Parse(textBox1.Text);
        try
        {
            await startSlaves(cts.Token, reqNumberOfThreads);
        }
        catch (OperationCanceledException)
        {
            MessageBox.Show("Canceled Starting Threads");
        }
        cts = null;

    }
    async Task startSlaves(CancellationToken ct, int threadNum)
    {

        List<Task<int>> allTasks = new List<Task<int>>();// ***Add a loop to process the tasks one at a time until none remain. 
        for (int x = 0; x < threadNum; x++)
        {
            allTasks.Add(beginSlaveOperation(ct, x));
        }
        // ***Add a loop to process the tasks one at a time until none remain. 
        while (allTasks.Count <= threadNum)
        {
            // Identify the first task that completes.
            Task<int> output = await Task.WhenAny(allTasks);
            allTasks.Remove(output);
            allTasks.Add(beginSlaveOperation(ct, output.Result));
        }
    }
    public async Task performExampleImportOperationThread(int inputVal, int whoAmI)
    {
        await Task.Delay(inputVal*100);
        System.Console.Write("Thread number" + whoAmI.ToString() + "has finished after "+inputVal.ToString()+" secs \n");
    }
    async Task<int> beginSlaveOperation(CancellationToken ct, int whoAmI)
    {
        Random random = new Random();
        int randomNumber = random.Next(0, 100);//Get command from microSched and remove it from sched
        await performExampleImportOperationThread(randomNumber, whoAmI);//perform operation
        return whoAmI;
    }

    private void button2_Click(object sender, EventArgs e)
    {
        if (cts != null)
        {
            cts.Cancel();
        }
    } 

Upvotes: 0

Servy
Servy

Reputation: 203829

Your beginSlaveOperation is marked as async, and you call it as if it were asynchronous, but you never actually await anything, so it is going to run synchronously. (Which makes its caller run synchronously, and its caller run synchronously, and so on, so that your entire application is running synchronously.)

Marking a method async doesn't magically make it run in a new thread. All it does is let you use the await keyword. If you don't ever use it, all you've done is execute a bunch of code synchronously and wrap the result in a completed task. You should even get a compiler warning for doing this.

You can fix that by having performExampleImportOperationThread be an async method and, instead of using Thread.Sleep(...), use await Task.Delay(...). That await performExampleImportOperationThread to make beginSlaveOperation actually be asynchronous.

Or you could just not do everything that you're doing and replace it all with a call to Parallel.For, which can set a MaxDegreesOfParallelism to limit you to a specific number of concurrent threads.

Upvotes: 1

Related Questions