Control.Invoke() vs IProgress / ReportProgress

本文关键字:IProgress ReportProgress vs Invoke Control | 更新日期: 2023-09-27 18:02:10

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 ?如果,为什么呢?这两种方法是否有可能产生"陷阱"?

IMHO, Invoke的方式更简单/需要更少的代码,例如,ReportProgress接受一个有几个字段的进度结构,特别是如果在任务的多个阶段报告进度,报告方法因此需要分支到给定阶段的适当报告。

Control.Invoke() vs IProgress / ReportProgress

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

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

Progress<>也与那些GUI类库一起工作,它使用一种更通用的方式来正确同步。由SynchronizationContext提供。属性,它依赖于GUI类库来安装提供程序。Winforms安装的WindowsFormsSynchronizationContext会自动在Post()方法中调用BeginInvoke(),在Send()方法中调用Invoke()。还有使async/await代码独立于UI实现的机制。

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

但是可以肯定的是,如果你硬编码你的类从System.Windows.Forms.UserControl派生,那么你很少使用Progress<>。除了感觉良好之外,当您将其移植到另一个GUI类库时,您将有更少的工作要做。