C#异步成就数据保存任务:解决任务取消与死锁问题
成就系统异步数据保存任务的死锁与实现疑问
需求背景
我正在为成就系统实现一个异步数据保存任务,该任务每分钟自动执行一次,也支持手动触发。
核心目标
- 同一时间仅能运行一个保存任务
- 新请求到达时,若存在正在运行的旧任务,立即取消旧任务
- 旧任务取消并释放资源后,再启动新任务
- 若已有排队待启动的任务,不再创建新任务
- 支持在主线程等待任务完成;若未创建新任务(因已有待启动任务),则等待该待启动任务完成
- 任务执行完毕后,释放所有占用资源
现有实现代码
数据仓库与保存任务调度器
public class AchievementSaveTask { private bool cancelled; private Task currentSource; private ConcurrentDictionary<int, AchievementData> aDatas; public AchievementSaveTask() => aDatas = MouseAchievementFile.Read(); public async Task StartWriting() { await CancelWrite(); currentSource = Task.Run(() => MouseAchievementFile.Write(aDatas, cancelled)); await currentSource; currentSource.Dispose(); currentSource = null; } public async Task CancelWrite() { if (currentSource != null) { cancelled = true; await currentSource; cancelled = false; currentSource.Dispose(); currentSource = null; } } }
文件写入类
public class MouseAchievementFile { private const string FOLDER = "MouseWorld/Achievement", FILE = "achievements.mdat"; public static void Write(ConcurrentDictionary<int, AchievementData> data, in bool cancelled = false) { var pathDir = Combine(Main.SavePath, FOLDER); if (!Directory.Exists(pathDir)) Directory.CreateDirectory(pathDir); //Execution stops somewhere here, why? using (var bw = new BinaryWriter(File.Open(Combine(pathDir, FILE), FileMode.Create))) { foreach ((_, var value) in data) { var datas = value.GetRawData(); foreach (var currData in datas) { if (!CheckWrite(bw, currData, cancelled)) { datas.Clear(); return; } } datas.Clear(); } } } private static bool CheckWrite(BinaryWriter bw, string source, in bool cancelled) { if (!cancelled) { bw.Write(source); return true; } else { bw.Flush(); bw.Close(); bw.Dispose(); return false; } } }
扩展方法
public static void HandleException(this Task source, Action<Exception> exceptionHandler) { source.ContinueWith(t => exceptionHandler(t.Exception), TaskContinuationOptions.OnlyOnFaulted); }
调用代码
if (wait) achievementSaveTask.StartWriting().Wait(); else achievementSaveTask.StartWriting().HandleException(t => MWMod.Instance.Logger.WarnFormat("MAChievementManager.SaveAchievements() - WARNING! Could not save dict. Exception: {0}\r{1}", t.Message, t.StackTrace));
问题排查与疑问
改用async-await改写后,控制台测试(仅输出字符串)正常,但实际文件写入场景出现冻结,冻结点大致在创建BinaryWriter阶段,推测是死锁问题。已知BinaryWriter不适合异步写入,但需要二进制格式写入数据,因此有以下疑问:
- 为何会出现冻结?
- 使用
StreamWriter能否避免该问题? - 能否使用
StreamWriter写入二进制数据?(目标是让输出文件无法被文本编辑器读取和编辑)
内容的提问来源于stack exchange,提问作者Unable error
相关产品推荐
相关产品推荐

