使用任务将结果计算与提交结果分离开来



我们正在考虑将计算结果的工作与提交结果的工作分开的API模式是:

    interface IResults { }
    class Results : IResults { }
    Task<IResults> CalculateResultsAsync(CancellationToken ct)
    {
        return Task.Run<IResults>(() => new Results(), ct);
    }
    void CommitResults(IResults iresults)
    {
        Results results = (Results)iresults;
        // Commit the results
    }

这将允许客户端拥有一个UI,该UI启动了一些结果的计算,并知道计算何时准备好,然后在那时决定是否提交结果。这主要是为了帮助我们处理在计算过程中,UI允许用户取消操作的情况。我们要确保:

  • 取消UI只在动作仍然可取消时显示(即一旦我们在CommitResults中,就没有回头路了),所以一旦CalculateResultsAsync任务完成,我们取下取消UI,只要用户没有取消,继续调用commit方法。
  • 我们不想有这样一种情况(即竞争条件),即用户点击取消,结果无论如何都被提交。
  • 客户端永远不会使用IResults,除非将其传递回CommitResults

问题:一般的问题是:这是一个好方法吗?具体来说:

  1. 它不觉得有这个分裂成两个方法,因为客户端从来没有检查IResults,他们只是把它交还给Commit方法。
  2. 这个问题有标准的解决方法吗?

这是一个非常标准的模式(如果不是理想的模式),特别是当您的Results对象是不可变的时候。我们经常在Visual Studio代码库中使用tpl的代码中这样做。当你的异步/并行逻辑在处理数据时,总是存在很多快乐,而那些变异的垃圾则存在于此之外。

如果你熟悉或听说过"Roslyn"项目,这是我们实际上鼓励人们使用的模式。这个想法是重构可以在后台异步处理,并产生一个对象,就像你的结果一样,它代表了应用重构的结果。然后,在UI线程上,任何人都可以取其中一个结果对象并应用它,这会更新你所有的文件以包含新的文本。

我确实发现整个IResults/Results的事情有点奇怪——它不清楚你是否使用这个来隐藏自己的实现。如果空接口和强制转换让您感到困惑,您可以考虑向IResult添加一个提交方法,由result对象实现。

我不确定您究竟为什么需要这种模式。对我来说,如果你在开始提交之前检查CancellationToken,你会得到完全相同的结果,更简单的界面。

最新更新