【发布时间】:2021-04-08 11:12:27
【问题描述】:
详情:
我有一个游戏,两个独立的 AI 互相对战。 每个 AI 都有自己的任务。 两个任务需要同时启动,需要带一些参数并返回一个值。 现在我想并行运行 100-200 个游戏(每两个任务)。
我现在遇到的问题是这两个任务没有一起启动。只要有一些免费资源,它们就会完全随机启动。
代码:
我目前的做法如下。
- 我有一个输入对象列表,其中包含一些参数。
- 使用 Parallel.ForEach,我为每个输入对象创建了一个游戏和两个 AI。
- 哪个 AI 先完成游戏,另一个 AI 会停止另一个 AI,使用 CancellationToken 进行相同的游戏。
- 所有返回值都保存在 ConcurrentBag 中。
因为每个游戏的两个 AI 任务没有一起启动,所以我添加了一个 AutoResetEvent。我希望我可以等待一个任务,直到第二个任务开始,但 AutoResetEvent.WaitOne 会阻止所有资源。所以 AutoResetEvent 的结果是第一个 AI 任务正在启动并等待第二个任务启动,但由于它们不再释放线程,所以它们永远等待。
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;
}
这是我等待第二个AI播放的AI的启动方法:
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));
}
}
还有简化的玩法
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;
}
有没有更好的方法来做到这一点,保留所有东西但确保每个游戏中的两个 KI-Tasks 同时启动?
【问题讨论】:
-
两位AI玩家如何互动?他们是否读写某些共享状态?这些读/写操作是使用
lock或其他同步原语同步的,还是无锁的? -
两个 AI 都与同一个游戏管理器交互。游戏管理器的某些部分(如包含游戏当前状态的四叉树)被锁定以防止错误。
-
是否可以选择使用coroutines而不是
Tasks来协调每对AI玩家?这个想法是将每个 AI 玩家公开为 iterator(一种返回IEnumerable并包含yield语句的方法)而不是Task,并且对于每个“展开”两个迭代器的游戏都有一个Task一步一个脚印。 -
两位玩家会不会总是轮流出手?所以 KI1 做了一个动作,然后 KI2、KI1……等等?两个 KI 都需要完全免费玩...
-
哦,我明白了。您能否在您的问题中包含一个 AI 播放器的异步
Start方法的简化示例,以便我们能够提供替代建议? (而不是协程)
标签: c# .net multithreading task