【问题标题】:Using Tasks as a way to separate computation of results from committing results使用任务作为将结果计算与提交结果分开的一种方式
【发布时间】:2013-05-10 20:28:29
【问题描述】:

我们正在考虑将计算某些结果的工作与提交这些结果的工作分开的 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 仅在操作仍可取消时显示(即,一旦我们在 CommitResults 中,就无法返回),所以一旦 CalculateResultsAsync 任务完成,我们就取消取消 UI,只要用户还没有取消,继续调用 commit 方法。
  • 我们不希望出现用户点击取消并且无论如何都会提交结果的情况(即竞争条件)。
  • 客户端永远不会使用IResults,只会将其传递回CommitResults

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

  1. 将这拆分为两个方法感觉不太对,因为客户端从不检查 IResults,他们只是将其交还给 Commit 方法。
  2. 是否有解决此问题的标准方法?

【问题讨论】:

    标签: c# asynchronous task-parallel-library


    【解决方案1】:

    这是一个非常标准的模式(如果不是理想的模式),尤其是当您的 Results 对象是不可变的时。我们在 Visual Studio 代码库中使用 TPL 的代码中定期执行此操作。当您的异步/并行逻辑处理数据时,总会有很多快乐存在,而变异的废话则与此无关。

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

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

    【讨论】:

    • 我们最终得到了一个具有 Commit 方法的 IResult(如您所建议的):这样就不需要有两个方法来代表一个操作。
    【解决方案2】:

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

    【讨论】:

    • 那么用户界面如何知道何时关闭显示取消按钮的 UI(因为如果用户无法取消操作,我不希望用户能够单击取消按钮)。
    • Matt 所做的一个优点是不给提交方法取消令牌,他们不能搞砸并认为他们必须支持取消。在 Roslyn 内部的一些地方,我们的方法具有很大的“不要在此点之后检查取消令牌”cmets,这绝对是不雅的。此外,如果提交具有 UI 线程亲和性,则进一步分解它是一个不错的模式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-22
    • 2016-04-14
    • 1970-01-01
    • 1970-01-01
    • 2018-11-20
    • 1970-01-01
    • 2011-02-27
    相关资源
    最近更新 更多