【问题标题】:Logic behind Thread.Sleep [duplicate]Thread.Sleep 背后的逻辑
【发布时间】:2017-12-01 12:39:57
【问题描述】:

在下面的 WPF 代码中,只有一个按钮和一个标签。我希望行为是这样的:我会看到按钮被禁用,并且标签上写着“Doing Stuff”,然后 UI 将被冻结。但这种情况并非如此。我什至没有看到“Doing Stuff”标签 UI 一直冻结到开始。你能告诉我这背后的逻辑吗?为什么 Thread.Sleep 会让 UI 立即冻结?

using System.Threading.Tasks;
using System.Threading;
using System.Windows;

namespace MessagePump
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void btnDoStuff_Click(object sender, RoutedEventArgs e)
    {
        btnDoStuff.IsEnabled = false;
        lblStatus.Content = "Doing Stuff";
        Thread.Sleep(4000);
        lblStatus.Content = "Not doing anything";
        btnDoStuff.IsEnabled = true;
    }
}
}
;

【问题讨论】:

  • 您应该使用调度程序:this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => { await Task.Delay(4000); });
  • Thread.Sleep(4000)替换为await Task.Delay(TimeSpan.FromSeconds(4));,并设置事件处理程序async以避免UI线程被阻塞。

标签: c# wpf


【解决方案1】:

Thread.Sleep 会阻塞当前线程——在本例中是 UI 线程——这意味着无法完成其他任务。 因此,在长时间阻塞操作完成之前,UI 线程现在无法执行任何消息,例如“刷新屏幕”。

会有一条类似 WM_PAINT 的待处理消息告诉 Windows 必须重新绘制 UI,但由于线程被睡眠操作阻塞,该消息无法执行。 如果您在 Thread.Sleep 之前添加以下代码行,您将看到标签将在 UI 冻结之前刷新

Application.DoEvents();

但请注意,在大多数情况下,阻塞 UI 线程并调用 Application.DoEvents 并不是一个好的解决方案。比阻塞 UI 线程和调用 DoEvents 更有可能解决某个问题的更好方法。 (例如,使用后台线程或任务执行长时间运行的操作,并使用事件通知 UI 该任务的进度)。

不应在 UI 线程上执行长时间运行的任务,因为您的应用程序将变得无响应。因此,最好将工作卸载到另一个线程。从 .NET 4 开始,首选方式是使用 TAP(基于任务的异步模式)。

我认为在您的问题中,您正在使用 Thread.Sleep 来“模拟”您想要执行的一些 CPU 密集型算法。 您可以通过启动一个新任务来做到这一点:

await Task.Run ( () => SomeLongRunningMethod())

【讨论】:

  • 好的,我明白了。但在执行 thread.sleep 行之前,我将标签的内容更改为“Doing Stuff”。为什么我在屏幕上看不到这种变化?那条线在 Thread.Sleep 线上方...是这么快还是什么?
  • " 我将标签的内容更改为 "Doing Stuff"。为什么我在屏幕上看不到该更改?" 因为 UI 线程必须重新绘制控件,所以你可以看到这种变化。但它不能,因为你让它进入睡眠状态。就像A队射门一样,数据增加了,但更新竞技场记分牌的人睡着了……
  • @Lyrk 你对实际发生的事情感到困惑。并不是用户界面“没有时间”进行更改。您将 lblStatus.Content 的值更改为“Doing Stuff”。这种变化确实发生了。这将是一个数据更改。 UI 现在需要直观地 进行更新以进行此更改。所以你的标签确实改变了它的内容,用户界面只是没有直观地显示出这种变化。正如 Frederik 在他的回答中提到的,有一条待处理的消息需要到达 UI 以通知它以可视方式显示更改。
  • @Lyrk 一个很好的查看方法是在您的 btnDoStuff_Click 事件中只使用lblStatus.Content = "Doing Stuff"; 紧跟lblStatus.Content = "Not doing anything";。然后运行调试器并逐步完成。您会注意到在lblStatus.Content = "Doing Stuff"; 之后,内容在视觉上没有变化。您可以看到 data 已更改,但 视觉上 消息尚未到达 UI。在您的 btnDoStuff_Click 事件完成之前,该消息不会到达 UI,然后您将看到 lblStatus.Content = "Not doing anything"; 以视觉方式应用。
  • @Lyrk 问题不是时间问题,而是操作顺序问题。在 btnDoStuff_Click 事件完成之前,UI 的待重绘消息不会开始。在 'lblStatus.Content = "Doing Stuff";' 之间投入多少时间并不重要和'Thread.Sleep(4000);'因为消息正在等待事件首先完成。这就是 Frederik 的回答有效的原因,因为他手动告诉 UI 在 btnDoStuff_Click 事件完成之前执行重绘事件。
【解决方案2】:

Thread.Sleep 阻塞 UI 线程。声明 Click 处理程序 async 并改为调用 Task.Delay

private async void btnDoStuff_Click(object sender, RoutedEventArgs e)
{
    btnDoStuff.IsEnabled = false;
    lblStatus.Content = "Doing Stuff";

    await Task.Delay(4000);

    lblStatus.Content = "Not doing anything";
    btnDoStuff.IsEnabled = true;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-31
    • 1970-01-01
    • 2012-11-24
    • 2010-10-01
    相关资源
    最近更新 更多