Control.Invoke() vs IProgress / ReportProgress
本文关键字:IProgress ReportProgress vs Invoke Control | 更新日期: 2023-09-27 18:02:10
UI可以通过IProgress
或BackgroundWorker.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
接受一个有几个字段的进度结构,特别是如果在任务的多个阶段报告进度,报告方法因此需要分支到给定阶段的适当报告。
您应该从您的努力中得到提示,使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类库时,您将有更少的工作要做。