C# 并行执行多对并发任务

C# 并行执行多对并发任务,c#,.net,multithreading,task,C#,.net,Multithreading,Task,详情: 我有一个游戏,两个独立的人工智能相互对抗。 每个AI都有自己的任务。 这两个任务需要同时启动,需要获取一些参数并返回一个值。 现在我想并行运行100-200个游戏(每两个任务) 我现在遇到的问题是,这两项任务不是同时开始的。只要有空闲资源,它们就完全随机启动 代码: 我目前的做法如下 我有一个inputObject列表,其中包括一些参数 使用Parallel.ForEach,我为每个inputobject创建一个游戏和两个游戏AI 无论哪个AI先完成游戏,都会使用Cancellatio

详情:

我有一个游戏,两个独立的人工智能相互对抗。 每个AI都有自己的任务。 这两个任务需要同时启动,需要获取一些参数并返回一个值。 现在我想并行运行100-200个游戏(每两个任务)

我现在遇到的问题是,这两项任务不是同时开始的。只要有空闲资源,它们就完全随机启动

代码:

我目前的做法如下

  • 我有一个inputObject列表,其中包括一些参数
  • 使用Parallel.ForEach,我为每个inputobject创建一个游戏和两个游戏AI
  • 无论哪个AI先完成游戏,都会使用CancellationToken停止另一个AI玩同一游戏
  • 所有返回值都保存在ConcurrentBag中
因为每个游戏的两个AI任务不是一起启动的,所以我添加了一个AutoResetEvent。我希望我可以等待一个任务,直到第二个任务开始,但是AutoResetEvent.WaitOne会阻塞所有资源。因此,AutoResteEvent的结果是,第一个AI任务正在启动并等待第二个任务启动,但由于它们不会再次释放线程,它们将永远等待

private ConcurrentBag TrainKis(列表人群){
ConcurrentBag resultCollection=新ConcurrentBag();
ConcurrentBag referenceCollection=新ConcurrentBag();
Parallel.ForEach(总体、个人=>
{
GameManager gm=新GameManager();
CancellationTokenSource c=新的CancellationTokenSource();
CancellationToken=c.令牌;
AutoResetEvent waitHandle=新的AutoResetEvent(假);
KI_base eaKI=新KI_dup(gm,individual.number,“KI-”+individual.number,Color.FromArgb(255,255,255));
KI_base referenceKI=新的KI_dup(gm,999,“REF-”+individual.number,Color.FromArgb(0,0,0));
Individual referenceIndividual=CreateIndividual(Individual.number,4002000);
var t1=referenceKI.Start(令牌、waitHandle、referenceIndividual)。ContinueWith(taskInfo=>{
c、 取消();
返回taskInfo.Result;
}).结果;
var t2=eaKI.Start(令牌、waitHandle、个人).ContinueWith(taskInfo=>{
c、 取消();
返回taskInfo.Result;
}).结果;
referenceCollection.Add(t1);
结果收集。添加(t2);
});
返回结果集合;
}
这是AI的启动方法,我在这里等待第二个AI的播放:

公共任务启动(取消令牌、自动恢复事件、个人){
i=_i;
gm.game.kis.Add(这个);
如果(gm.game.kis.Count>1){
_are.Set();
返回任务。运行(()=>Play(_-ct));
}
否则{
_是。WaitOne();
返回任务。运行(()=>Play(_-ct));
}
}
以及简化的播放方法

公共覆盖单个播放(取消令牌ct){
Console.WriteLine($“{player.username}已启动。”);
while(常数.TOWN_NUMBER*0.8>player.towns.Count | | player.towns.Count==0){
试一试{
线程睡眠((int)(Constants.TOWN_GROTH_SECONDS*1000+10));
}
捕获(例外情况){
Console.WriteLine($“{player.username}错误:{u ex}”);
}
//以下是AI的操作(为了更好地概述,我删除了它们)
如果(ct.IsCancellationRequested){
返回i;
}
}

如果(Constants.TOWN_NUMBER*0.8我的建议是更改
Play
方法的签名,使其返回一个
任务
,而不是
个人
,并将对
线程.Sleep
的调用替换为
等待任务.Delay
。这个小小的更改应该会对响应产生显著的积极影响在AI玩家中,因为没有线程会被他们阻止,而
ThreadPool
线程的小池将得到最佳利用

public override async Task<Individual> Play(CancellationToken ct)
{
    Console.WriteLine($"{player.username} started.");
    while (Constants.TOWN_NUMBER * 0.8 > player.towns.Count || player.towns.Count == 0)
    {
        //...
        await Task.Delay((int)(Constants.TOWN_GROTH_SECONDS * 1000 + 10));
        //...
    }
}

这样,您将获得最大的并发性,这可能并不理想,因为您机器的CPU或RAM或CPU RAM带宽可能会饱和。您可以寻找限制并发异步操作数量的方法。

两个AI播放器如何相互交互?它们是否读写到某个共享状态?是吗这些读/写操作使用
lock
或其他同步原语进行同步,或者它们是无锁的?两个AI都与同一个游戏管理器交互。某些部分(如包含游戏当前状态的四叉树)为防止错误,游戏管理器的密码被锁定。是否可以使用
任务
s来协调每对AI玩家?其思想是将每一位AI玩家公开为一个(返回
IEnumerable
并包含
yield
语句的方法)而不是
任务
,并为每个“展开”的游戏指定一个
任务
两个迭代器一步一个脚印。然后两个玩家总是轮流移动吗?那么KI1做一个动作,然后KI2,KI1…等等?两个KI都需要完全自由地玩…哦,我明白了。你能在你的问题中包括一个AI玩家异步
Start
方法的简化示例吗?这样我们就可以提供一个替代建议?(而不是协同程序)@theoretisch简言之,找到所有
.Wait()
.ResultTask[] tasks = population.Select(async individual =>
{
    GameManager gm = new GameManager();
    CancellationTokenSource c = new CancellationTokenSource();

    //...

    var task1 = referenceKI.Start(token, waitHandle, referenceIndividual);
    var task2 = eaKI.Start(token, waitHandle, individual);

    await Task.WhenAny(task1, task2);
    c.Cancel();
    await Task.WhenAll(task1, task2);
    var result1 = await task1;
    var result2 = await task2;
    referenceCollection.Add(result1);
    resultCollection.Add(result2);
}).ToArray();

Task.WaitAll(tasks);