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()
.
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.
CodePudding user response:
My best guess is that the asynchronous functionality of the bot
object is serialized internally. If you decompile the class from which it was created, you might find inside code like this:
class Bot
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
public async Task Connect()
{
await _semaphore.WaitAsync();
try
{
// Do the connect business
}
finally { _semaphore.Release(); }
}
public async Task SendTokens()
{
await _semaphore.WaitAsync();
try
{
// Do the send-tokens business
}
finally { _semaphore.Release(); }
}
}
If that's the case, your only realistic option is to create a dedicated Bot
instance for each of your tasks. Even this is not guaranteed to work. For example the _semaphore
might be declared as static
. In that case your next attempt could be to start each of your tasks as a separate process. Which still might not work if the Bot
implementation uses a
If this is in fact the intended outcome, here's one approach you could try. First, since the Bot
class itself can't be modified, make an async
Extension method for it instead. 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 we have to work with. We do NOT have access
// to changing this class, but we know it's got a lot of async methods in it.
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; }
}