none
多线程更新UI问题 RRS feed

  • 问题

  • 有个button按下去需要很长时间实行,我想在button事件之初UI中显示正在处理请稍候,实行完后再显示已完成。

            private void btnExport_Click(object sender, RoutedEventArgs e)
            {
                lblExportInfo.Dispatcher.Invoke(new Action(() =>
                 { 
                     lblExportInfo.Content = "导出中,请稍后..."; 
                 }));


                System.Threading.Thread.Sleep(3000);

            lblExportInfo.Dispatcher.Invoke(new Action(() =>
                 { 
                     lblExportInfo.Content = "已完成."; 
                 }));

    }

    就这么简单代码,但是为什么显示结果是按下去后空白提示,然后等3秒后显示已完成,我想要的结果是点下去后马上显示导入中...3秒后显示已完成。这个很麻烦吗?奇怪,帮忙看看,多谢

    2012年3月15日 10:24

答案

  •     HI:

        你应该去看一下 WPF 的线程模型。

        你这个函数是注册 button 的点击事件,当 button 点击的时候,这段代码实际上是在 UI 线程上面执行的。调用 System.Threading.Thread.Sleep(3000) 实际上是将UI线程睡住了。

        而提示“导出中”这个信息,是Invoke异步执行的。导致了显示“导出中”的这段代码还没来得及执行, UI 线程就已经 Sleep 了,所以界面没有任何反应。过了3秒钟,UI线程醒来,

    弹出“导出中”这个信息。可是后面又紧跟着

    一个显示“已完成”的信息,马上会把原来的信息覆盖掉。实际上这段代码的流程是:

       UI 线程 Sleep -->  显示“导出中” --> 显示"已完成"。而最后两个信息显示的间隔非常快,所以你只看到了"已完成"。

       你在点击事件中其实是可以直接写  lblExportInfo.Content = "导出中,请稍后...";  的,不需要异步,接着弹出进度条,不让 UI 接受任何输入,然后开一个后台线程去做耗时处理,当后台线程执行完毕后,回到 UI 线程,移除进度条,再调用 lblExportInfo.Content = "已完成."。 这样就OK了。给你一个小示例:

       xaml 代码如下:

    <Window x:Class="Client.MainWindow" Title="MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Grid Width="500" Height="500">
            <Label x:Name="lblMessage" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0 50 0 0"></Label>
            <Button Content="导出数据" Width="80" Height="25" VerticalAlignment="Bottom" HorizontalAlignment="Center" 
                    Margin="0 0 0 50" Click="Button_Click"></Button>
        </Grid>
    </Window>

      后台代码如下:

    // 点击按钮时启用后台线程导出数据,显示提示信息 private void Button_Click(object sender, RoutedEventArgs e) { // 显示提示信息 this.lblMessage.Content = "导出中,请稍后..."; // 弹出进度条,不让界面响应输入,简单起见,这里直接禁用界面 this.IsEnabled = false; // 开启后台线程,执行导出操作 BackgroundWorker bgWorker = new BackgroundWorker(); bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork); bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted); bgWorker.RunWorkerAsync(); }

    // 后台线程的工作代码 private void bgWorker_DoWork(object sender, DoWorkEventArgs e) { // 执行耗时的导出操作,这里将后台线程Sleep 3秒,模拟耗时 System.Threading.Thread.Sleep(3000); }

    // 后台线程的导出操作完成后,显示提示信息 private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.lblMessage.Content = "导出完成"; this.IsEnabled = true; }

            希望对你有所帮助。

                                                                       Bean


    2012年3月16日 17:38

全部回复

  • Invoke方法是异步的。所以,当被加到主线程中时主线程被强制sleep了。醒来后立刻执行导致了后续两个被执行的非常快,你看到的只是后一个了。
    2012年3月15日 14:23
  • 理论上异步就可以更新到界面,可是为什么显示内容还是空白?直到3s后才会出来最后更新的文字
    2012年3月16日 1:13
  •     HI:

        你应该去看一下 WPF 的线程模型。

        你这个函数是注册 button 的点击事件,当 button 点击的时候,这段代码实际上是在 UI 线程上面执行的。调用 System.Threading.Thread.Sleep(3000) 实际上是将UI线程睡住了。

        而提示“导出中”这个信息,是Invoke异步执行的。导致了显示“导出中”的这段代码还没来得及执行, UI 线程就已经 Sleep 了,所以界面没有任何反应。过了3秒钟,UI线程醒来,

    弹出“导出中”这个信息。可是后面又紧跟着

    一个显示“已完成”的信息,马上会把原来的信息覆盖掉。实际上这段代码的流程是:

       UI 线程 Sleep -->  显示“导出中” --> 显示"已完成"。而最后两个信息显示的间隔非常快,所以你只看到了"已完成"。

       你在点击事件中其实是可以直接写  lblExportInfo.Content = "导出中,请稍后...";  的,不需要异步,接着弹出进度条,不让 UI 接受任何输入,然后开一个后台线程去做耗时处理,当后台线程执行完毕后,回到 UI 线程,移除进度条,再调用 lblExportInfo.Content = "已完成."。 这样就OK了。给你一个小示例:

       xaml 代码如下:

    <Window x:Class="Client.MainWindow" Title="MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Grid Width="500" Height="500">
            <Label x:Name="lblMessage" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0 50 0 0"></Label>
            <Button Content="导出数据" Width="80" Height="25" VerticalAlignment="Bottom" HorizontalAlignment="Center" 
                    Margin="0 0 0 50" Click="Button_Click"></Button>
        </Grid>
    </Window>

      后台代码如下:

    // 点击按钮时启用后台线程导出数据,显示提示信息 private void Button_Click(object sender, RoutedEventArgs e) { // 显示提示信息 this.lblMessage.Content = "导出中,请稍后..."; // 弹出进度条,不让界面响应输入,简单起见,这里直接禁用界面 this.IsEnabled = false; // 开启后台线程,执行导出操作 BackgroundWorker bgWorker = new BackgroundWorker(); bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork); bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted); bgWorker.RunWorkerAsync(); }

    // 后台线程的工作代码 private void bgWorker_DoWork(object sender, DoWorkEventArgs e) { // 执行耗时的导出操作,这里将后台线程Sleep 3秒,模拟耗时 System.Threading.Thread.Sleep(3000); }

    // 后台线程的导出操作完成后,显示提示信息 private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.lblMessage.Content = "导出完成"; this.IsEnabled = true; }

            希望对你有所帮助。

                                                                       Bean


    2012年3月16日 17:38