0

I'm creating a Selenium bot as a WPF application. I'm using Tasks to create bots and then tasks to perform things on bot.

        foreach (string account in accounts)
        {
            count++;

            _ = Task.Run(async () =>
            {
                Bot bot = new Bot();

               //here is long code for what should bot do ASYNC

               //example
                await bot.Connect();                   
                await bot.SendTokens(); 
                await bot.SayHi(); 
            });
            await Task.Delay(600);
        }

I have array of accounts and I loop through them.

Let's say accounts.Length = 10 => I want to create 10 tasks that will 'have their own life' and run async inside of them. My problem is that Tasks wait for each other. Loop will create 10 tasks gradually (because of Task.Delay(600)) => Bot will be created and start bot.Connect(); Lets say it takes always same amount of time to complete Connect().

Logically first Task should be completed 600ms faster than second Task. Problem is that program waits until all Connect() are completed. Then it starts gradually all SendTokens() then complete them, but first task won't start SayHi() until SendTokens() of last bot in list is completed.

Why is that happening, I don't understand that. I tried setting

ThreadPool.SetMaxThreads(500, 500);

but that does nothing for me.

Here is example of live debug output, starts LoadOptions() gradually many times, but first LoadOptions() should be already completed and next method should be already started but it is waiting for all LoadOptions to end. I don't use in code Task.WaitAll().

Debug output

Can anyone help me fix this issue and tell me what was wrong? I want to learn and know in future. Thank you very much and have a nice day.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
SPIRINN
  • 61
  • 6
  • Can you show the contents of the relevant functions of the Bot class? – Stefan Jun 25 '22 at 20:03
  • Here is example of method in Bot => https://prnt.sc/rY6AZDWvlklC They are all Tasks, few also have await keyword in them. Methods used in my post do not exist, they were only for example show. – SPIRINN Jun 25 '22 at 20:07
  • So in other words, the undesirable behavior is that all asynchronous operations are serialized. You want them to run concurrently, but instead they run the one after the other sequentially, correct? – Theodor Zoulias Jun 25 '22 at 20:30
  • @TheodorZoulias Yes, I would like to have all Tasks created in loop running concurrently, but keep code inside created Task asynchronous. – SPIRINN Jun 25 '22 at 20:40
  • *"but keep code inside created Task asynchronous"* -- the correct term is probably "concurrent". You can check out this question if you want: [What is the difference between concurrency, parallelism and asynchronous methods?](https://stackoverflow.com/questions/4844637/what-is-the-difference-between-concurrency-parallelism-and-asynchronous-methods) For most people, asynchronous means *"without blocking the current thread"*. – Theodor Zoulias Jun 25 '22 at 20:44
  • I also found term 'Parallelism' as relevant. https://stackoverflow.com/a/48530284/15278218 – SPIRINN Jun 25 '22 at 21:07
  • @TheodorZoulias, if you can justify WPF tag for this question, you can very well add it yourself using edit link https://stackoverflow.com/posts/72756961/edit. maybe you can also manage to convince OP to create [mcve] which will make clear if wpf is involved in some way or not – ASh Jun 25 '22 at 21:23
  • Could you include in the question the code of the `Bot` class? – Theodor Zoulias Jun 27 '22 at 05:22

1 Answers1

0

As I understand it, your loop launches N bots and starts them on a "Life of their own". The loop provides a spacing of 600 ms between Bot launches. The Bot class contains various async tasks, and for any given Bot there is an example start sequence of Connect, SendTokens, and SayHi where each of these three methods must complete before the next one starts.

But apart from that, each bot should be taking care of its own internal tasks concurrently while other bots are taking care of theirs. A typical concurrent timeline might look like this:

concurrent timeline

Here's one approach you could try to achieve this outcome. If the Bot class comes from a library where it can't be modified you can still make an async Extension method for it instead. (I'll show it that way just in case. Otherwise just make it a normal method of the Bot class.) Note that the return type is void; the Bot has a life of its own per your post! We don't wait for it to live its life. HOWEVER it has internal business where one async activity must run to completion before the next one starts, and this extension method accounts for that.

static class Extensions
{
    public static async void PostInitAsyncRequest(this BlackBoxBot bot)
    {
        await bot.Connect();
        await bot.SendTokens();
        await bot.SayHi();
    }
}

All your WPF application needs to do is call the launchAllBots method which waits 600 ms between launches.

async void launchAllBots() 
{
    foreach (string account in new string[] 
    {"Jessie", "Ross", "Kensho", "Ernest", "Amy", "Andrew", "Baxter", "Jibo", "Buddy"})
    { 
        // Has its own life so DO NOT AWAIT
        var bot = new BlackBoxBot();
        bot.Account = account;
        bot.PostInitAsyncRequest();

        // But DO await the delay to space out Bot launches
        await Task.Delay(600);          
    }
}

SIMULATION In order to mock this startup timeline, the following fake Bot class was used:

// A crude mock of the 'Bot' class with internal async methods.
class BlackBoxBot
{
    public async Task Connect() 
    {
        Console.WriteLine($"{DateTime.Now.ToString("ss:fff")} {Account}: {nameof(Connect)}");
        await Task.Delay(_rando.Next(400, 800));
    }
    public async Task SendTokens()
    {
        Console.WriteLine($"{DateTime.Now.ToString("ss:fff")} {Account}: {nameof(SendTokens)}");
        await Task.Delay(_rando.Next(400, 800));
    }
    public async Task SayHi()
    {
        Console.WriteLine($"{DateTime.Now.ToString("ss:fff")} {Account}: {nameof(SayHi)}");
        await Task.Delay(_rando.Next(400, 800));
    }

    static Random _rando = new Random();

    public string Account { get; internal set; }
}
IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • 1
    Thank you very much. I forgot to write, that Bot.cs is created by me, I can access and change code without problem. Your picture shows my needed functionallity. I will put that functionality into method and then run only that method in loop. – SPIRINN Jun 26 '22 at 16:48
  • Thx for the feedback! I've made a few minor changes more in line with that. – IVSoftware Jun 26 '22 at 17:13
  • It is weird that if I use 5 bots with delay after creating one. Then it is good they are all running concurentlly on outside and async inside. Problem is when I use 25 bots for example, then behaviour is again bad. I think there are some problems with threading pool even if I set max tasks to basically unreachable in my code – SPIRINN Jun 26 '22 at 18:19
  • I'll see if I can repro that. – IVSoftware Jun 26 '22 at 18:20
  • Is there any way to send you private message? If it is allowed. – SPIRINN Jun 26 '22 at 18:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/245930/discussion-between-ivsoftware-and-spirinn). – IVSoftware Jun 26 '22 at 18:27