Progress<T> 与 Action<T> 有何不同? (C#)
How is Progress<T> different from Action<T> ? (C#)
我一直在使用 Progress<T>
,想知道是否可以用 Action<T>
代替它。
在下面的代码中,使用它们中的每一个来报告进度,即 ReportWithProgress()
或 ReportWithAction()
,对我来说没有任何明显的区别。 progressBar1
如何增加,字符串如何写入输出 window,它们看起来是一样的。
// WinForm application with progressBar1
private void HeavyIO()
{
Thread.Sleep(20); // assume heavy IO
}
private async Task ReportWithProgress()
{
IProgress<int> p = new Progress<int>(i => progressBar1.Value = i);
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
p.Report(i);
}
}
private async Task ReportWithAction()
{
var a = new Action<int>(i => progressBar1.Value = i);
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Action : " + i);
a(i);
}
}
但是Progress<T>
不能是重新发明轮子。应该有它被实施的原因。谷歌搜索 "c# Progress vs Action" 并没有给我太多帮助。 Progress 与 Action 有何不同?
不同之处在于,对于 Progress<T>
,您有一个事件,其中多个侦听器可以侦听进度,而 Progress<T>
确实会在构造实例时捕获 SynchonizationContext
,因此不会如果在 GUI 线程中创建,则需要调用到 GUI 线程。
您还可以将多个侦听器添加到 Action<T>
(感谢@Servy 指出了这一点),但随后每个侦听器都会在调用操作的线程中执行。
考虑以下扩展示例,其中 Progress<T>
有效,但 Action<T>
有效 throw an exception:
private async Task ReportWithProgress()
{
var p = new Progress<int>(i => progressBar1.Value = i);
p.ProgressChanged += (s, e) => progressBar2.Value = e;
Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
((IProgress<int>)p).Report(i);
}
});
}
private async Task ReportWithAction()
{
var a = new Action<int>(i => progressBar1.Value = i);
a += i => progressBar2.Value = i;
Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Action : " + i);
a(i);
}
});
}
从不同的线程调用 progressBar1.Value = i
导致可怕的 "cross-thread operation not valid" exception. The Progress
class, on the other hand, dispatches the event to the synchronization context 在构建时被捕获:
// simplified code, check reference source for actual code
void IProgress<T>.Report(T value)
{
// post the processing to the captured sync context
m_synchronizationContext.Post(InvokeHandlers, value);
}
private void InvokeHandlers(object state)
{
// invoke the handler passed through the constructor
m_handler?.Invoke((T)state);
// invoke the ProgressChanged event handler
ProgressChanged?.Invoke(this, (T)state);
}
这确保对进度条、标签和其他 UI 元素的所有更新都在一个(且唯一的)GUI 线程上完成。
因此,只有在后台线程的 Progress
class 外部 实例化才有意义,在 UI 线程:
void Button_Click(object sender, EventArgs e)
{
// since this is a UI event, instantiating the Progress class
// here will capture the UI thread context
var progress = new Progress<int>(i => progressBar1.Value = i);
// pass this instance to the background task
Task.Run(() => ReportWithProgress(progress));
}
async Task ReportWithProgress(IProgress<int> p)
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
p.Report(i);
}
}
我一直在使用 Progress<T>
,想知道是否可以用 Action<T>
代替它。
在下面的代码中,使用它们中的每一个来报告进度,即 ReportWithProgress()
或 ReportWithAction()
,对我来说没有任何明显的区别。 progressBar1
如何增加,字符串如何写入输出 window,它们看起来是一样的。
// WinForm application with progressBar1
private void HeavyIO()
{
Thread.Sleep(20); // assume heavy IO
}
private async Task ReportWithProgress()
{
IProgress<int> p = new Progress<int>(i => progressBar1.Value = i);
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
p.Report(i);
}
}
private async Task ReportWithAction()
{
var a = new Action<int>(i => progressBar1.Value = i);
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Action : " + i);
a(i);
}
}
但是Progress<T>
不能是重新发明轮子。应该有它被实施的原因。谷歌搜索 "c# Progress vs Action" 并没有给我太多帮助。 Progress 与 Action 有何不同?
不同之处在于,对于 Progress<T>
,您有一个事件,其中多个侦听器可以侦听进度,而 Progress<T>
确实会在构造实例时捕获 SynchonizationContext
,因此不会如果在 GUI 线程中创建,则需要调用到 GUI 线程。
您还可以将多个侦听器添加到 Action<T>
(感谢@Servy 指出了这一点),但随后每个侦听器都会在调用操作的线程中执行。
考虑以下扩展示例,其中 Progress<T>
有效,但 Action<T>
有效 throw an exception:
private async Task ReportWithProgress()
{
var p = new Progress<int>(i => progressBar1.Value = i);
p.ProgressChanged += (s, e) => progressBar2.Value = e;
Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
((IProgress<int>)p).Report(i);
}
});
}
private async Task ReportWithAction()
{
var a = new Action<int>(i => progressBar1.Value = i);
a += i => progressBar2.Value = i;
Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Action : " + i);
a(i);
}
});
}
从不同的线程调用 progressBar1.Value = i
导致可怕的 "cross-thread operation not valid" exception. The Progress
class, on the other hand, dispatches the event to the synchronization context 在构建时被捕获:
// simplified code, check reference source for actual code
void IProgress<T>.Report(T value)
{
// post the processing to the captured sync context
m_synchronizationContext.Post(InvokeHandlers, value);
}
private void InvokeHandlers(object state)
{
// invoke the handler passed through the constructor
m_handler?.Invoke((T)state);
// invoke the ProgressChanged event handler
ProgressChanged?.Invoke(this, (T)state);
}
这确保对进度条、标签和其他 UI 元素的所有更新都在一个(且唯一的)GUI 线程上完成。
因此,只有在后台线程的 Progress
class 外部 实例化才有意义,在 UI 线程:
void Button_Click(object sender, EventArgs e)
{
// since this is a UI event, instantiating the Progress class
// here will capture the UI thread context
var progress = new Progress<int>(i => progressBar1.Value = i);
// pass this instance to the background task
Task.Run(() => ReportWithProgress(progress));
}
async Task ReportWithProgress(IProgress<int> p)
{
for (int i = 0; i <= 100; i++)
{
await Task.Run(() => HeavyIO());
Console.WriteLine("Progress : " + i);
p.Report(i);
}
}