[英]Control.Invoke() vs IProgress / ReportProgress
可以使用IProgress
或BackgroundWorker.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);
}
}
但是,这也可以通过将委托传递给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
方式更简单/所需的代码更少,尤其是如果在任务的多个阶段报告进度并且报告方法因此需要分支到相应的报告上给定的阶段。
您应该从使方法2实际编译的努力中得到启发。 花了一段时间,不是吗? 我看到您反复编辑摘录。 您得到的另一个提示是,到达那里的唯一方法是从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,您将不知道为什么,因为该异常发生在与bug距离很远的代码中。
但是可以肯定的是,如果您对类进行硬编码以从System.Windows.Forms.UserControl派生,那么Progress <>几乎没有用。 除了那种感觉很好的感觉,当您将其移植到另一个GUI类库时,您要做的工作会更少。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.