异步命令行机器人程序冻结问题排查:Superhero模块异常
我编写了一款命令行程序,让Lifeguard和Superhero两个机器人在22×22矩阵中随机移动,直至抵达带有#符号的方格。Superhero的方向选择并非均匀分布,66%的概率选择向北移动。我可通过菜单异步启动两个机器人,但Lifeguard的数据很快返回,程序却因Superhero陷入冻结。我本以为异步编程不会导致冻结,请问问题出在哪里?
using System.Diagnostics; class Program { public static async Task Main(string[] args) { Map map = new Map(); Roboter l = new Roboter(map.defaultMap, "Lifesaver", 'L', 9); Roboter s = new Roboter(map.defaultMap, "Superhero", 'S', 10); map.spreadRoboters(l, s); View view = new View(map.defaultMap); Stopwatch watch = new Stopwatch(); Random rand = new Random(); string input = "start"; while (input != "exit") { Console.WriteLine("choose an option:"); Console.WriteLine("1 start asynchronously"); Console.WriteLine("2 show Map"); Console.WriteLine("3 exit."); input = Console.ReadLine(); switch (input) { case "1": await StartTaskAsync(l, s, rand, watch); break; case "2": view.displayMap(); break; case "exit": input = "exit"; break; default: input = "exit"; break; } } } static async Task StartTaskAsync(Roboter l, Roboter s, Random r, Stopwatch watch) { Task<string> lifesaverTask = StartLifesaverAsync(l, r, watch); Task<string> superheroTask = StartSuperheroAsync(s, r, watch); List<Task<string>> taskList = new List<Task<string>> { lifesaverTask, superheroTask }; /* Task<string> completedTask = await Task.WhenAny(taskList); string result = await completedTask; Console.WriteLine(result);*/ while (taskList.Count > 0) { Task<string> taskResult = await Task.WhenAny(taskList); if (taskResult == lifesaverTask) { Console.WriteLine(lifesaverTask.Result); } else if (taskResult == superheroTask) { Console.WriteLine(superheroTask.Result); } taskList.Remove(taskResult); } } static async Task<string> StartLifesaverAsync(Roboter l, Random r, Stopwatch watch) { return await Task.Run(() => { int steps = 1; string[] directions = new string[] { "NORTH", "SOUTH", "WEST", "EAST" }; watch.Start(); while (l.state != true) { int zufall = r.Next(0, 4); string direction = directions[zufall]; l.move(direction, l.map); steps++; } watch.Stop(); long time = watch.ElapsedMilliseconds; l.reset(1, 1); return "Lifesaver: steps: " + (steps).ToString() + ", time: " + time.ToString() + "ms"; }); } static async Task<string> StartSuperheroAsync(Roboter s, Random r, Stopwatch watch) { return await Task.Run(() => { int steps = 1; string[] directions = new string[] { "NORTH", "NORTH", "NORTH", "NORTH", "NORTH", "NORTH", "SOUTH", "WEST", "EAST" }; watch.Start(); while (s.state != true) { int zufall = r.Next(0, 9); string direction = directions[zufall]; s.move(direction, s.map); steps++; } watch.Stop(); long time = watch.ElapsedMilliseconds; s.reset(20, 20); return "Superhero: steps: " + (steps).ToString() + ", time: " + time.ToString() + "ms"; }); } }
1. 共享Random实例导致的线程安全问题
Random类本身不是线程安全的,你在两个后台任务中共享了同一个Random实例。当两个Task.Run中的代码同时调用r.Next()时,会破坏随机数生成的内部状态,导致生成的随机数重复或逻辑混乱。这会让Superhero的移动方向陷入单一循环,无法接近目标方格,最终任务永远无法结束,程序看起来像冻结了。
解决办法:给每个任务单独创建Random实例,或者使用线程安全的随机数生成方式(比如RandomNumberGenerator)。
2. 共享Stopwatch的计时混乱问题
两个任务都调用watch.Start()和watch.Stop(),但Stopwatch是共享实例。当Lifeguard任务结束调用Stop()后,Superhero任务再调用Start()会重新计时,导致时间统计完全错误。更关键的是,多线程操作同一个Stopwatch也可能引发不可预期的行为。
解决办法:每个任务使用独立的Stopwatch实例,避免共享。
3. 移动逻辑的潜在死循环
Superhero有66%的概率向北移动,如果它的初始位置在(20,20),而目标方格在地图南侧,它大概率会一直向北移动远离目标;如果移动到地图边界后,move方法没有处理边界限制(比如无法移动或反弹),会导致while (s.state != true)循环无限执行,任务永远无法完成。
解决办法:
- 检查
move方法,确保边界移动逻辑正确,避免机器人卡在边界; - 添加最大步数限制,比如超过10万步就强制终止循环,防止任务无限期运行;
- 调整方向概率,确保Superhero有合理的概率向目标方向移动。
4. 异步代码的冗余包装
StartLifesaverAsync和StartSuperheroAsync方法内部用await Task.Run(),但可以直接返回Task.Run的结果,不需要额外的async/await包装(这不是冻结的直接原因,但会增加不必要的状态机开销)。
修改示例:
static Task<string> StartLifesaverAsync(Roboter l) { return Task.Run(() => { Stopwatch watch = Stopwatch.StartNew(); int steps = 1; Random r = new Random(); string[] directions = new string[] { "NORTH", "SOUTH", "WEST", "EAST" }; while (l.state != true) { int zufall = r.Next(0, 4); string direction = directions[zufall]; l.move(direction, l.map); steps++; // 添加最大步数限制 if (steps > 100000) break; } watch.Stop(); long time = watch.ElapsedMilliseconds; l.reset(1, 1); return $"Lifesaver: steps: {steps}, time: {time}ms"; }); }
内容的提问来源于stack exchange,提问作者Z.J

