【问题标题】:Reporting from Background Thread Using IProgress<T>使用 IProgress<T> 从后台线程报告
【发布时间】:2013-01-24 03:17:16
【问题描述】:

所有,我有一个昂贵的任务,我使用 .NET4.5 下的 async/await 关键字来完成。我通过Progress&lt;T&gt; 类从后台线程报告进度。

我传入IProgress&lt;T&gt; 的对象是ProgressInfo,我将其创建为单例类,以避免在运行期间大量创建和处理此类对象的开销。类是

public class ProgressInfo : IDisposable
{
    public static ProgressInfo instance = null;
    public WorkbookProgressInfo workbookinfo { get; set; }
    public string progressMessage { get; set; }
    public int progressPercentage { get; set; }

    // Default constructor which has a few overloads 
    // for the class members above.
    protected ProgressInfo()
    {
    }
    ...

    // The default instance creator for the class. 
    // This also has overloads to allow changes to the 
    // class variables.
    public static ProgressInfo Instance()
    {
        if (instance == null)
            instance = new ProgressInfo();
        return instance;
    }
    ...
}

我通过ReportProgress方法报告进度并将我的IProgress&lt;ProgressInfo&gt;设置为

IProgress<CostEngine.ProgressInfo> progressIndicator =
    new Progress<CostEngine.ProgressInfo>(ReportProgress);

后台线程的报告通常使用全局ProgressInfo progressInfo 和全局IProgress&lt;ProgressInfo&gt; progressIndicator 来完成

...
progressInfo = new ProgressInfo.Instance(workbookinfo, message, n);
progressIndicator.Report(progressInfo);
...

问题在于,对于小型且快速执行的运行,传递给ReportProgress 的ProgressInfo 对象会随着ReportProgress 的执行而变化,因此我进行了测试

if (progressInfo.workbookinfo != null)
{
    // Do stuff <- here progressInfo.workbookinfo is changing to null!
}

如何避免此问题,同时将报告进度的费用降到最低?

感谢您的宝贵时间。

【问题讨论】:

  • 在这种情况下使用单例似乎非常非常奇怪。对于初学者,您的 ProgressInfo 类不是线程安全的。我不会那样做,你也不应该这样做。您是否真的验证过 ProgressInfo 实例的创建是一个性能问题?还是您只是认为可能是一个?
  • 我只是认为它可能是一个。我正在从大型嵌套循环中报告并创建这些(尽管相当轻量级)对象数千次(取决于运行)似乎是我可以通过使用单例来避免的开销......我也不确定事实如何Progressinfo 不是线程安全的,这对这个问题有任何影响,因为同步上下文在使用 IProgress&lt;T&gt; 时得到了完全处理。感谢您的宝贵时间。
  • 非常糟糕的主意。通过尝试使此类具有所有锁定等线程安全性,您将产生更大的开销。您在这里所做的是过早的优化。没有什么好东西来自它。使用分析器优化您已验证为问题的内容。
  • workbookinfo 更改为null 的事实很好地证明了您的类不是线程安全的。同步上下文与此无关。
  • 每次都创建 ProgressInfo,不要使用单音。要提高性能,只需尝试减少进度通知次数

标签: c# multithreading .net-4.5 async-await progress


【解决方案1】:

您在这里尝试优化应用程序的错误部分。

几千个对象对于一个应用程序来说毫无意义。创建它们很可能不会成为您的应用程序中的性能问题。然而,可能的问题是 UI 的更新。如果在短时间内发生成千上万的进度报告,您将不断更新您的 UI。更新 UI 需要时间,所以如果你遇到瓶颈,它就会在这里。

这里的正确方法是只报告您需要报告的内容。
例如,当您在一分钟内发生 60.000 次迭代并且您报告每一次迭代时,您将尝试每毫秒更新 UI。
您的进度条真的需要 1000 FPS 吗?我对此表示怀疑。
最好每秒只报告一次。但是,每秒报告一次会产生自己的开销。

我在应用程序中所做的是在百分比发生变化时立即报告。
这将产生最多 100 个报告,无论您实际有多少次迭代。
在上面的示例中,我将每 600 次迭代报告一次。

为了简化这一点,我实际上创建了一个名为 ProgressReporter 的类。
在其构造函数中,它采用一个工厂委托来创建 ProgressInfo 对象和迭代总数。
它提供了您在当前迭代中传递的ReportProgress 方法。
该类在内部存储先前报告的百分比,并在调用此方法时计算新的百分比。只有当不同时,它才会使用工厂委托来创建新的ProgressInfo 并在IProgress&lt;T&gt; 实例上调用Report。

顺便说一句:如果您的后台操作不需要几分钟而是几小时,您可以将其更改为不报告百分比何时发生变化,而是在百分比的第一位或第二位小数发生变化时报告。
这里的要点是:找到一定数量的报告,每隔几秒就给用户一个反馈。

【讨论】:

  • 谢谢丹尼尔,非常感谢您的时间。我实际上执行了一个 UI 更新模块(1000 条记录),处理一条记录可能需要几毫秒,所以我并没有压倒 UI。我已经测试了您的建议,并且对性能绝对没有影响(如果我们将默认时间刻度设为秒)。再次感谢。一切顺利,周末愉快。
  • @Killercam:如果对性能没有影响,则意味着您的担忧从一开始就是不合理的 :) 这正是您应该只优化您测量的内容的原因。否则,您经常会在错误的地方进行优化 - 有时甚至会使事情变得更糟。
  • 好建议...尝试第二次猜测 CLR 会在性能方面做什么有点愚蠢。
猜你喜欢
  • 2013-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-24
相关资源
最近更新 更多