Progress <T>与Action <T>有何不同?(C#)

dix*_*hom 5 c# generics delegates progress

我一直在使用Progress<T>,想知道是否可以将其替换Action<T>

在下面的代码中,使用它们中的每个来报告进度(即ReportWithProgress()或)ReportWithAction()对我没有任何明显的影响。如何progressBar1增加字符串,如何将字符串写入输出窗口,它们看起来是相同的。

// 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);
    }
} 
Run Code Online (Sandbox Code Playgroud)

但是Progress<T>,这不能彻底改变方向。实施它应该有一个原因。谷歌搜索“ c#Progress vs Action”并没有给我太多帮助。进展与行动有何不同?

Gro*_*roo 15

progressBar1.Value = i从不同的线程调用会导致可怕的“跨线程操作无效”异常。该Progress班,在另一方面,调度该事件的同步环境建设的瞬间捕捉到:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

这确保了对进度条、标签和其他 UI 元素的所有更新都在(一个且唯一的)GUI 线程上完成。

因此,它才有意义,以实例化Progress以外的后台线程,一个被称为在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);
    }
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*Fin 5

不同之处在于,Progress<T>您有一个事件,其中多个侦听器可以侦听进度,并在构造实例时Progress<T>捕获该事件SynchonizationContext,因此如果在 GUI 线程中创建,则不需要调用 GUI 线程。
您还可以向 an 添加多个侦听器Action<T>(感谢 @Servy 指出这一点),但每个侦听器都会在调用该操作的线程中执行。

考虑下面的扩展示例,其中Progress<T>会起作用,但Action<T>抛出异常

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);
            }
        });
} 
Run Code Online (Sandbox Code Playgroud)

  • `Action&lt;T&gt;` 是一个多播委托。调用它时可以调用任意数量的方法。 (3认同)