Starward游戏安装模块中的并发任务处理问题解析
Starward Game Launcher for miHoYo - 米家游戏启动器 项目地址: https://gitcode.com/gh_mirrors/st/Starward
问题背景
在Starward项目的游戏安装模块中,开发团队发现了一个偶发性问题:当游戏安装完成后,界面按钮有时不会正常更新为"开始游戏"状态。经过深入分析,这个问题源于安装任务并发处理机制的设计缺陷。
技术原理分析
问题的核心在于InstallGameService.cs
文件中的任务调度逻辑。原代码使用了一个循环来启动多个并发任务,但没有妥善处理这些任务的完成状态。具体来说:
-
异步任务立即执行:C#中的异步方法在被调用后会立即开始执行,而不需要显式等待。这意味着循环可能还未结束,前面启动的任务已经完成。
-
线程计数器问题:代码使用
_concurrentExecuteThreadCount
作为线程计数器,在finally块中检查该计数器是否为0来决定是否调用CurrentTaskFinished
方法。由于任务完成速度可能快于循环执行速度,导致计数器多次归零,引发CurrentTaskFinished
方法的重复调用。 -
线程安全问题:
CurrentTaskFinished
方法中的逻辑不是线程安全的,重复调用会导致状态管理混乱,最终表现为UI更新异常。
解决方案
经过技术分析,团队提出了以下改进方案:
-
使用Task.WhenAll统一管理:将所有并发任务收集到一个数组中,使用
Task.WhenAll
方法统一等待所有任务完成。 -
重构任务启动逻辑:将任务启动和完成处理封装到一个独立的异步方法中,确保逻辑清晰。
-
线程安全处理:在确保所有任务完成后,再调用状态更新方法,避免竞态条件。
改进后的核心代码如下:
_ = RunTasksAsync();
return;
async Task RunTasksAsync()
{
_cancellationTokenSource = new CancellationTokenSource();
var tasks = new Task[Environment.ProcessorCount];
for (int i = 0; i < Environment.ProcessorCount; i++)
{
tasks[i] = ExecuteTaskItemAsync(_cancellationTokenSource.Token);
}
try
{
await Task.WhenAll(tasks).ConfigureAwait(false);
}
finally
{
CurrentTaskFinished();
}
}
技术要点
-
异步编程模式:正确理解和使用C#的异步编程模型是解决此类问题的关键。
Task.WhenAll
提供了优雅的并发任务管理方式。 -
线程安全设计:在多线程环境下,状态管理必须考虑线程安全问题。原设计中的计数器检查方式存在明显的竞态条件风险。
-
异常处理:使用try-finally结构确保无论任务执行成功与否,都能正确进行资源清理和状态更新。
经验总结
这个案例展示了在异步编程中常见的陷阱:
- 异步方法的立即执行特性容易被忽视
- 并发任务的状态管理需要特别小心
- UI更新必须确保在主线程且状态一致时进行
通过这次问题修复,项目团队对异步编程和并发控制有了更深入的理解,也为类似场景提供了可参考的解决方案模式。
Starward Game Launcher for miHoYo - 米家游戏启动器 项目地址: https://gitcode.com/gh_mirrors/st/Starward
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考