简体   繁体   English

Control.Invoke()与IProgress / ReportProgress

[英]Control.Invoke() vs IProgress / ReportProgress

The UI can be passed information from an asynchronous task using IProgress or BackgroundWorker.ReportProgress : 可以使用IProgressBackgroundWorker.ReportProgress从异步任务向UI传递信息:

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);
    }
}

However, this can also be done by passing a delegate to a UI object's BeginInvoke method ( Invoke if we want to block): 但是,这也可以通过将委托传递给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();
    }
}        

Should the dedicated progress reporting mechanisms be preferred over Invoke ? 是否应该使用专用的进度报告机制而不是Invoke If, so why? 如果是,那为什么呢? Are there any likely 'gotchas' arising from either approach? 两种方法都有可能产生“陷阱”吗?

IMHO, the Invoke way is simpler / requires less code compared to, say, a ReportProgress accepting a progress struct with several fields, especially if progress is reported at multiple stages of the task and the reporting method thus needs to branch to the appropriate reporting for a given stage. 恕我直言,与接受具有多个字段的进度结构的ReportProgress相比, Invoke方式更简单/所需的代码更少,尤其是如果在任务的多个阶段报告进度并且报告方法因此需要分支到相应的报告上给定的阶段。

You should have gotten a cue from your struggles to make Approach2 actually compile. 您应该从使方法2实际编译的努力中得到启发。 Took a while, didn't it? 花了一段时间,不是吗? I saw you repeatedly editing the snippet. 我看到您反复编辑摘录。 Another cue you got was that the only way to get there was to derive your class from UserControl. 您得到的另一个提示是,到达那里的唯一方法是从UserControl派生您的类。

Which is the problem with Begin/Invoke(), it can only work when you have access to a Control object. 这是Begin / Invoke()的问题,它仅在您有权访问Control对象时才起作用。 Code inside a library often (and should) have no idea what the UI looks like. 库中的代码通常(并且应该)不知道UI的外观。 It might not even be implemented in Winforms, could be used in a WPF or Universal app for example. 它甚至可能没有在Winforms中实现,例如可以在WPF或Universal应用程序中使用。

Progress<> works with those GUI class libraries as well, it uses a more universal way to properly synchronize. Progress <>也可以与这些GUI类库一起使用,它使用一种更通用的方式来正确同步。 Provided by the SynchronizationContext.Current property, it relies on the GUI class library to install a provider. 由SynchronizationContext.Current属性提供,它依赖GUI类库来安装提供程序。 The one that Winforms installs, WindowsFormsSynchronizationContext, automatically calls BeginInvoke() in its Post() method and Invoke() in its Send() method. Winforms安装的WindowsFormsSynchronizationContext自动在其Post()方法中调用BeginInvoke(),在其Send()方法中自动调用Invoke()。 Also the mechanism that makes async/await code independent from the UI implementation. 也是使异步/等待代码独立于UI实现的机制。

There is one disadvantage to Progress<>, it can completely fail to get the job done in a very hard to diagnose way. Progress <>有一个缺点,它可能完全无法以很难诊断的方式完成工作。 The object must be created by code that runs on the UI thread of an app. 该对象必须由在应用程序的UI线程上运行的代码创建。 If it is not then SynchronizationContext.Current doesn't have a value and the ProgressChanged event is going to fire on an arbitrary threadpool thread. 如果不是,则SynchronizationContext.Current没有值,并且ProgressChanged事件将在任意线程池线程上触发。 Kaboom if you try to update the UI with an event handler, you won't know why because the exception occurs in code that's far removed from the bug. Kaboom如果尝试使用事件处理程序更新UI,您将不知道为什么,因为该异常发生在与bug距离很远的代码中。

But sure, if you hardcode your class to derive from System.Windows.Forms.UserControl then you have little use for Progress<>. 但是可以肯定的是,如果您对类进​​行硬编码以从System.Windows.Forms.UserControl派生,那么Progress <>几乎没有用。 Other than the feel-good feeling that you'll have less work to do when you ever port it to another GUI class library. 除了那种感觉很好的感觉,当您将其移植到另一个GUI类库时,您要做的工作会更少。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM