【问题标题】:How to update UI (text fields) during long running function?如何在长时间运行功能期间更新 UI(文本字段)?
【发布时间】:2013-06-12 15:31:33
【问题描述】:

我知道这个问题可能没有意义,而且我很难想出一种方法来解释它,所以我将展示一段代码来提供帮助。我在 Visual Studio Express 2010 上使用 Winforms:

private void button1(object sender, EventArgs e)
    {
        txtOutput.Text += "Auto-collecting variables. This may take several minutes";
        string v = foo();
        txtOutput.Text += "\n" + v;
        string b = bar();
        txtOutput.Text += "\n" + b;

        txtOutput.SelectionStart = txtOutput.Text.Length;
        txtOutput.ScrollToCaret(); //scrolls to the bottom of textbox
    }

所以基本上,当用户单击 button1 时,我希望“自动收集变量...”显示在文本框中,然后执行 foo(),显示,然后执行 bar(),然后然后显示出来。

当前发生的是 foo() 和 bar() 执行,然后在 foo() 和 bar() 执行后立即显示所有内容(函数需要几分钟)。有没有办法解决这个问题,或者有解决办法吗?

编辑:C# 的版本是 4.0。如果我更新到 4.5 或 5.0,没有 .NET 4.5/5.0 的计算机可以运行 .exe 吗?

【问题讨论】:

  • 我已更新标题 - 随时恢复。请注意,上下文“控制台输出”或各种顺序日志/文件中常用的“输出”。在 UI 情况下,以下词更常见:“update”/“refresh”/“show”。
  • @AlexeiLevenkov 啊!这是一个更好的标题。谢谢!

标签: c# winforms .net-4.0


【解决方案1】:

C# 5.0 让这一切变得微不足道。

使用Task.Run 在后台线程中执行长时间运行的任务,并使用await 作为 UI 线程中的延续执行该方法的其余部分,而不会在异步任务期间阻塞 UI 线程。

private async void button1(object sender, EventArgs e)
{
    txtOutput.Text += "Auto-collecting variables. This may take several minutes";
    string v = await Task.Run(() => foo());
    txtOutput.Text += "\n" + v;
    string b = await Task.Run(() => bar());
    txtOutput.Text += "\n" + b;

    txtOutput.SelectionStart = txtOutput.Text.Length;
    txtOutput.ScrollToCaret(); //scrolls to the bottom of textbox
}

您可以在 C# 4.0 中这样做:(第一个解决方案将被编译器转换为类似的东西。)

private  void button1(object sender, EventArgs e)
{
    txtOutput.Text += "Auto-collecting variables. This may take several minutes";
    Task.Factory.StartNew(() => foo())
        .ContinueWith(t => txtOutput.Text += "\n" + t.Result
            , TaskScheduler.FromCurrentSynchronizationContext())
        .ContinueWith(t => bar())
        .ContinueWith(t =>
        {
            txtOutput.Text += "\n" + t.Result;
            txtOutput.SelectionStart = txtOutput.Text.Length;
            txtOutput.ScrollToCaret(); //scrolls to the bottom of textbox
        }
            , TaskScheduler.FromCurrentSynchronizationContext());
}

【讨论】:

  • 好吧,我已经尝试了一段时间并且已经升级到 .NET 5.0,但是由于某种原因,Task.Run 没有定义。 Error 1 'System.Threading.Tasks.Task' does not contain a definition for 'Run'我使用了错误的命名空间吗?
  • @BrianR 没有 .NET 5。有 C# 4.0 和 .NET 4.5。如果您没有 Task.Run 的定义,则表示您使用的是 .NET 4.0,而不是 4.5。
  • @Servy 抱歉,我对 C# 和 .NET 一点也不熟悉。将项目属性更改为使用 .NET 4.5 后,您的解决方案可以完美运行。谢谢。
  • @BrianR 别担心; C#、.NET、Visual Studio 等的版本控制非常混乱。对此感到困惑是完全可以理解的。
【解决方案2】:

使用BackgroundWorker 类在不阻止 UI 更新的情况下进行处理。它具有可用于将进度信息传输到 UI 线程的事件。

【讨论】:

    【解决方案3】:

    根据 .NET 的版本,您可以使用BackgroundWorker(4.0 前)或Tasks(4.0 - 3.5 后的附加组件)...仅举几例。

    后台工作人员伪代码:

    var backgroundWorker = new BackgroundWorker()
    
    method
    {
        //Update UI
        backgroundWorker.RunWorkAsync()
    }
    
    asyncworkmethod
    {
        //do main logic
    }
    
    asynccompletemethod
    {
        //Update UI to say done
    }
    

    任务伪代码:

    method
    {
         //Update UI
         TaskFactory.StartNew(()=>DoWork).ContinueWith((previousTask)=>UpdateUIToSayDone)
    }
    

    而且,如果您使用的是 4.5,那么您可以使用 async/await 关键字,但这只是围绕任务的语法糖(主要是......)。 Servy 已经有一个很好的例子,不过如果你采用这种方法

    【讨论】:

    • 1) 澄清问题放在 cmets 中,而不是帖子中。 2)如果您没有完整的答案,请不要发布“更多详细信息”的答案。当您有完整的答案时发布答案。事实上,这充其量只是一个评论。
    • 没有反对票,我刚刚检查过(更新:现在有一个)。另外,我认为这更好,因为它提到了Tasks,这比 BGWorkers 更好方式,方式
    • 谁在 4 分钟前删除了我的评论?我之前的评论是maybe that's just some mistake?
    • @Servy 我会看看能不能在 meta 上找到一些东西,但我说那是 BS。我一直看到这种回答方法。您不能否认 SO 部分是“快速上手”类型的地方。
    • @JustinPihony 如果您要发布答案,它应该回答问题。这是 SO 的基本概念,我可以在 meta 上找到 很多 示例来解释为什么不适合发布 cmets、澄清问题或其他非答案作为答案。您打算将其编辑为适当的答案并不能使初始帖子适当。您似乎对此感到困惑的是发布一个有效的答案来回答问题,但不会深入很多,并且在编辑中添加了深度。这并不违反规则。
    【解决方案4】:

    使用后台进程(阅读其他答案)是正确的方法,但如果您正在寻找一种非常快速的解决方法,您可以在更新文本框后致电Application.DoEvents()。在大多数情况下,此调用将导致您的表单更新以反映您所做的更改。

    【讨论】:

    • “在大多数情况下”除非它没有并且您的整个应用程序崩溃(或更糟),因为您滥用了您的消息循环。
    • @Servy:在什么情况下应用会崩溃?
    • 这是如何滥用消息循环的(您是否有一些导致应用程序崩溃的示例)?它只是暂时返回 UI 线程来处理消息。这不是推荐的方法,但在某些情况下,这可能比切换到后台进程更适合实施。
    • @Knaģis 使用函数的时候恰当的使用很重要,这不是恰当的使用。尤其重要的是,在使用DoEvents 时,它不能重入;您需要禁用表单上可能向消息队列添加新事件的控件。如果您在此期间禁用整个表单,那么它可以工作,而且是非常糟糕的做法。从根本上说,要真正让它正常工作是非常非常困难的。它比其他替代品困难得多。如果你对它的工作原理不够了解,最好不要使用它。
    • 需要注意的是DoEvents。如果您没有禁用所有输入,则用户可能会单击另一个按钮或更改其他状态。通常,最好在开始漫长的过程之前禁用所有输入,并在完成后启用它们。例外情况包括取消按钮或长时间运行的操作,这些操作作为单独的批处理有效地进行,不会受到用户继续使用应用程序的影响。
    【解决方案5】:

    txtOutput.Update() 应该做你想做的,但你应该考虑使用后台线程来完成长时间运行的任务而不阻塞 UI 线程。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-02
      • 2013-05-28
      • 2017-01-11
      • 1970-01-01
      • 1970-01-01
      • 2018-10-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多