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

时间:2018-02-05 14:48:50

标签: 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);
    }
} 

Progress<T>不能重新发明轮子。应该有一个理由来实施它。谷歌搜索“c#Progress vs Action”并没有给我太多帮助。进步与行动有何不同?

2 个答案:

答案 0 :(得分:2)

从另一个线程调用progressBar1.Value = i会导致可怕的"cross-thread operation not valid"异常。另一方面,Progress类将事件发送到构建时捕获的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线程上完成。

因此,在UI线程上调用的方法中实例化后台线程的Progress 是唯一有意义的:

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

答案 1 :(得分:1)

不同之处在于,对于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);
            }
        });
}