这里有两个链接试图解释事情是如何工作的:
(1) (2)
现在,我将尝试尽快解释它。在 Windows 窗体应用程序中发生的大部分事情都发生在单个线程中,通常 Main() 运行在同一个线程中。如果打开 Program.cs,您会看到 Main() 有一行如下所示:
Application.Run(new Form1());
如果您在任何时候调试应用程序并检查调用堆栈,您将看到它将追溯到该 Run 方法。这意味着 Windows 窗体应用程序实际上是 Run 方法的连续运行。那么,Run在做什么呢?Run 正在吃一个消息队列,Windows 通过该消息队列向它发送消息。Run 然后将这些消息发送到正确的控件,这些控件本身会执行添加与按下的键相对应的文本、重绘自身等操作。请注意,所有这些都发生在与单个线程一起运行的无限循环期间,因此您正在输入天气或者简单地移动窗口,这些消息的负载被传递到应用程序,应用程序反过来处理它们并做出相应的反应,所有这些都在单个线程中。控件还可以通过队列向自己发送消息,甚至您可以通过 Control.BeginInvoke 将消息放入泵中。这些控件所做的其中一件事是根据发生的情况引发事件。因此,如果您单击一个按钮,您为处理该单击而编写的代码将最终间接地由 Application.Run 方法运行。
现在,您的代码正在发生的事情是,即使您将进度条的可见状态更改为可见,然后更新其值,您也将其可见性更改为 false,所有这些都使用相同的方法。这意味着只有在您离开该方法后,Application.Run() 才能继续迭代和消费消息队列,有效地要求进度条更新其显示。发生这种情况时,您已经将进度条的可见性设置为 false,这是您在退出方法之前所做的最后一件事。DoEvents() 是一种快速而肮脏的解决方法,因为它读取队列中的消息并处理它们。我真的不喜欢使用它,因为它会带来重入问题。
使用线程是一个很好的解决方案,但我建议在这种情况下使用 ThreadPool 线程而不是自定义线程,因为我倾向于仅在我的长寿命线程数量有限并且我需要的情况下使用自定义线程控制它们的生命周期。使用线程的最简单和最实用的方法是使用 BackgroundWorker 组件,尽管如果您想真正了解发生了什么,我建议您通过了解如何使用委托进行 Windows 窗体多线程处理的痛苦。