【问题标题】:How is Progress<T> different from Action<T> ? (C#)Progress<T> 与 Action<T> 有何不同? (C#)
【发布时间】:2018-07-15 11:04:43
【问题描述】:

我一直在使用Progress&lt;T&gt;,想知道是否可以将其替换为Action&lt;T&gt;

在下面的代码中,使用它们中的每一个来报告进度,即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&lt;T&gt; 不可能是轮子的再发明。实施它应该是有原因的。谷歌搜索“c# Progress vs Action”并没有给我太多帮助。 Progress 与 Action 有何不同?

【问题讨论】:

  • 我建议您将 HeavyIO 更改为 async Task HeavyIO() { await Task.Delay(20); } 以便至少您正在调用一个任务
  • Progress&lt;T&gt; 在构造它的上下文中调用该操作,这使您可以与 UI 交互而无需烦人的调用代码。
  • 您在Progress 的文档中找到了哪些信息,为什么无法回答您的问题?
  • 从不同的线程调用progressBar1.Value = i 会导致可怕的"cross-thread operation not valid" 异常。

标签: c# generics delegates progress


【解决方案1】:

从不同的线程调用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);
    }
}

【讨论】:

  • @ChrFin:嗯,你的表单控件绑定到的“唯一”线程。
【解决方案2】:

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

考虑以下扩展示例,其中Progress&lt;T&gt; 可以工作,但Action&lt;T&gt; 可以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);
            }
        });
} 

【讨论】:

  • Action&lt;T&gt; 是多播委托。调用时可以调用任意数量的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
  • 1970-01-01
相关资源
最近更新 更多