【问题标题】:Control.Invoke() vs IProgress / ReportProgressControl.Invoke() 与 IProgress / ReportProgress
【发布时间】:2015-09-30 10:30:37
【问题描述】:

UI 可以使用IProgressBackgroundWorker.ReportProgress 从异步任务传递信息:

class Approach1 : UserControl
{
    enum Stage { INIT, STATUS, DATA, TIME, ... }
    struct ProgressObject
    {
         int StatusCode;
         int SecondsRemaining;
         IList<int> Data;
         Stage CurrentStage;
    }

    TextBox Status;

    async Task DoWork1(IProgress<ProgressObject> progress) 
    {
         await Task.Run( () =>
         {
             progress.Report(new ProgressObject(0, 0, null, Stage.INIT));
             int code = DoSomething();
             progress.Report(new ProgressObject(code, 0, null, Stage.STATUS));
             IList<int> Data = ...;
             progress.Report(new ProgressObject(0, 0, Data, Stage.DATA));
             int Seconds = ...;
             progress.Report(new ProgressObject(0, time, null, Stage.TIME));
         });
    }

    void ReportProgress(ProgressObject progress)
    {
        switch (progress.CurrentStage)
        {
            case Stage.CODE:
                Status.Text = DecodeStatus(progress.StatusCode);
                break;
            // And so forth...
        }
    }

    async void Caller1(object sender, EventArgs e)
    {
        var progress = new Progress<ProgressObject>(ReportProgress);
        await DoWork2(progress);
    }
}

但是,这也可以通过将委托传递给 UI 对象的 BeginInvoke 方法来完成(如果我们想阻止,则为 Invoke):

class Approach2 : UserControl
{
    Textbox Status;

    int StatusCode
    {
        set
        {
            BeginInvoke(new Action( () => Status.Text = DecodeStatus(value));
        }
    }

    // Imagine several other properties/methods like the above:
    int SecondsRemaining;
    void UpdateData(IList<int> data);

    async Task DoWork2()
    {
        await Task.Run( () =>
        {
            StatusCode = DoSomething();
            UpdateData(...);
            SecondsRemaining = ...;
        });
    }

    async void Caller2(object sender, EventArgs e)
    {
        await DoWork1();
    }
}        

是否应该优先使用专用的进度报告机制而不是 Invoke?如果,那为什么?这两种方法是否存在任何可能的“陷阱”?

恕我直言,与ReportProgress 接受具有多个字段的进度结构相比,Invoke 方式更简单/需要更少的代码,特别是如果在任务的多个阶段报告进度并且报告方法因此需要分支到给定阶段的适当报告。

【问题讨论】:

  • “问题代码”与 BeginInvoke 与 Task.Run 无关,因为它们的目的相同。您不妨写:BeginInvoke(new Action(() =&gt; progress.Report(value)); 以获得更公平的比较 - 在​​第一个示例中,“要做的事情”在其他地方完成。
  • 我认为您误解了 - 问题是关于使用 Invoke 与框架的专用进度报告功能。在一种情况下,调用上下文是使用Invoke 访问的,在另一种情况下,它被IProgress 实现隐式捕获。两个示例都使用Task.Run
  • 请使代码保持一致,除非它应该不同。因为 Task 可以在没有 ReportProgress/IProgress(“更多代码”)的情况下完成,或者 Invoke 可以用它来完成。
  • 我更新了示例代码,以更好地反映具有复杂进度报告的真实用例。这个问题是关于实现明显相同结果的两种模式中的哪一种应该被首选,以及为什么。

标签: c# multithreading winforms asynchronous


【解决方案1】:

您应该从使 Approach2 真正编译的努力中得到启示。花了一段时间,不是吗?我看到你反复编辑 sn-p。您得到的另一个提示是,到达那里的唯一方法是从 UserControl 派生您的类。

Begin/Invoke() 的问题是什么,它只能在您可以访问 Control 对象时起作用。库中的代码通常(并且应该)不知道 UI 是什么样的。它甚至可能不会在 Winforms 中实现,例如可以在 WPF 或 Universal 应用程序中使用。

Progress 也适用于那些 GUI 类库,它使用更通用的方式来正确同步。由 SynchronizationContext.Current 属性提供,它依赖于 GUI 类库来安装提供程序。 Winforms 安装的 WindowsFormsSynchronizationContext 会自动在其 Post() 方法中调用 BeginInvoke() 并在其 Send() 方法中调用 Invoke()。也是使异步/等待代码独立于 UI 实现的机制。

Progress 有一个缺点,它可能完全无法以一种很难诊断的方式完成工作。对象必须由在应用程序的 UI 线程上运行的代码创建。如果不是,则 SynchronizationContext.Current 没有值,并且 ProgressChanged 事件将在任意线程池线程上触发。 Kaboom 如果您尝试使用事件处理程序更新 UI,您将不知道原因,因为异常发生在远离错误的代码中。

但可以肯定的是,如果您将类硬编码为从 System.Windows.Forms.UserControl 派生,那么 Progress 几乎没有用处。除了当您将它移植到另一个 GUI 类库时,您将有更少的工作要做的感觉良好。

【讨论】:

  • 感谢您的澄清!在我的真实代码中,我已经使用了 UserControl;该模式第一次尝试。听起来第一种模式的主要优点是,如果将工作方法移到库中并且让调用者实现自己的进度方法,则无需更改任何内容。
猜你喜欢
  • 1970-01-01
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多