Details:
I have a game with two independent AIs playing agains each other. Each AI has its own task. Both tasks need to start at the same time, need to take some parameters and return a value. Now I want to run 100-200 games (with each two tasks) in parallel.
The problem that I now have is that the two tasks are not started together. They are started completely random, whenever there are some free resources.
Code:
My current approach is like following.
- I have a list of inputobjects which include some of the parameter.
- With Parallel.ForEach I create for each inputobject a game and two AIs for the game.
- Whichever AI finishes the game first stops the other AI, playing the same game, with a CancellationToken.
- All returned values are saved in a ConcurrentBag.
Because with just that the two AI-Tasks for each game are not started together, I added an AutoResetEvent. I hoped that I could wait with one task until the second task has started but instead the AutoResetEvent.WaitOne blocks all resources. So the result with AutoResetEvent is that the first AI-Tasks are starting and waiting for the second task to start, but since they do not free the threads again they wait forever.
private ConcurrentBag<Individual> TrainKis(List<Individual> population) {
ConcurrentBag<Individual> resultCollection = new ConcurrentBag<Individual>();
ConcurrentBag<Individual> referenceCollection = new ConcurrentBag<Individual>();
Parallel.ForEach(population, individual =>
{
GameManager gm = new GameManager();
CancellationTokenSource c = new CancellationTokenSource();
CancellationToken token = c.Token;
AutoResetEvent waitHandle = new AutoResetEvent(false);
KI_base eaKI = new KI_Stupid(gm, individual.number, "KI-" + individual.number, Color.FromArgb(255, 255, 255));
KI_base referenceKI = new KI_Stupid(gm, 999, "REF-" + individual.number, Color.FromArgb(0, 0, 0));
Individual referenceIndividual = CreateIndividual(individual.number, 400, 2000);
var t1 = referenceKI.Start(token, waitHandle, referenceIndividual).ContinueWith(taskInfo => {
c.Cancel();
return taskInfo.Result;
}).Result;
var t2 = eaKI.Start(token, waitHandle, individual).ContinueWith(taskInfo => {
c.Cancel();
return taskInfo.Result;
}).Result;
referenceCollection.Add(t1);
resultCollection.Add(t2);
});
return resultCollection;
}
This is the start method of the AI where I wait for the second AI to play:
public Task<Individual> Start(CancellationToken _ct, AutoResetEvent _are, Individual _i) {
i = _i;
gm.game.kis.Add(this);
if (gm.game.kis.Count > 1) {
_are.Set();
return Task.Run(() => Play(_ct));
}
else {
_are.WaitOne();
return Task.Run(() => Play(_ct));
}
}
And the simplified play method
public override Individual Play(CancellationToken ct) {
Console.WriteLine($"{player.username} started.");
while (Constants.TOWN_NUMBER*0.8 > player.towns.Count || player.towns.Count == 0) {
try {
Thread.Sleep((int)(Constants.TOWN_GROTH_SECONDS * 1000 + 10));
}
catch (Exception _ex) {
Console.WriteLine($"{player.username} error: {_ex}");
}
//here are the actions of the AI (I removed them for better overview)
if (ct.IsCancellationRequested) {
return i;
}
}
if (Constants.TOWN_NUMBER * 0.8 <= player.towns.Count) {
winner = true;
return i;
}
return i;
}
Is there a better way of doing this, keeping all things but ensure that the two KI-Tasks in each game are started at the same time?