You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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不适合异步写入,但需要二进制格式写入数据,因此有以下疑问:

  1. 为何会出现冻结?
  2. 使用StreamWriter能否避免该问题?
  3. 能否使用StreamWriter写入二进制数据?(目标是让输出文件无法被文本编辑器读取和编辑)

内容的提问来源于stack exchange,提问作者Unable error

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 19:47:18