C# 启动新任务时取消上一个 IO 保存任务

C# Cancel the previous IO save Task when starting a new one

提问人:Unable error 提问时间:4/29/2023 最后编辑:Unable error 更新时间:4/30/2023 访问量:76

问:

我正在尝试为我的成就系统异步保存数据。此任务每 1 分钟从主代码运行一次,但也可以在任何其他时间运行。


我想实现这些目标:

  1. 一次只能运行一个保存任务。
  2. 一旦保存请求到达,就应立即取消上一个保存任务(如果存在)。
  3. 新任务应在上一个任务完成取消且上一个任务的资源已清除后启动。
  4. 如果队列中存在尚未启动的保存任务,则不应创建新任务。
  5. 请求启动任务时,应该可以等待任务在主线程上完成。如果由于上一个任务尚未启动而尚未创建新任务,则需要等待尚未启动的任务完成。
  6. 当前任务执行完毕后,应释放其使用的所有资源。

到目前为止,我已经尝试了不同的选项,这是最后一个:

数据仓库和保存任务计划程序:

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 以二进制形式写入数据吗?(目标是使用文本编辑器使输出文件不可读和不可编辑)
C# .NET 异步 IO 任务

评论

0赞 Charlieface 4/30/2023
BinaryWriter不兼容异步,无法使用 有什么原因吗?CancellationToken
0赞 Unable error 4/30/2023
@Charlieface 不,我没有特别的理由使用令牌。我尝试了很多选项,取消令牌只是之前一些选项的回声。我随时准备删除它,我只需要传递实际变量的能力即可取消写入过程。关于BinaryWriter...我真的不明白异步在 c# 中是如何工作的,但我一行一行地写,我可以随时通过关闭写入流来停止它。因此,我决定传递一个变量来表示任务已被取消。也许我错过了什么,如果我错了,请纠正我
0赞 Charlieface 4/30/2023
不,我的意思是你为什么要使用,它基本上已被弃用?CancellationToken“是处理这个问题的正确方法,只是不接受或处理它BinaryWriterBinaryWriter
0赞 Unable error 4/30/2023
@Charlieface 老实说,我对这个事实有点困惑。为什么它被弃用?我不知道这件事,也没有找到信息。关键方面是我想编写无法通过文本文档编辑的数据。通过这种方式,我可以保护自己免受想要修复成就的标准用户的侵害。我也可以使用任何其他方法来代替 BinaryWriter,但恐怕我没有想出更好的东西。如果有更好的解决方案,我会很乐意接受
0赞 Charlieface 4/30/2023
对不起,你是对的,没有被弃用,但是没有异步方法 stackoverflow.com/q/10315316/14868997 我只是不明白你在这种情况下只使用什么,不确定你要保护什么。 具有异步方法,并且可以传入。StreamWriterStreamWriterCancellationToken

答: 暂无答案