UI线程阻塞

9

我创建了一个简单的WPF应用程序,并在默认窗口中添加了一个按钮。当我单击该按钮时,会调用一个模拟的长时间工作方法(使用Thread.Sleep(15000)模拟)。我正在尝试使按钮异步执行,但是尽管遵循在线示例,但按钮和整个窗口在我单击后锁定,并且一直保持到Thread.Sleep(...)完成。

有什么想法为什么会发生这种情况吗?

以下是代码:

private void button1_Click(object sender, RoutedEventArgs e)
{
   DoSomeAsyncWork();
}

private void DoSomeAsyncWork()
{
     System.Windows.Threading.Dispatcher.Run();
     Thread thread = new System.Threading.Thread(
         new System.Threading.ThreadStart(
          delegate()
          {
               Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => Thread.Sleep(15000)));
          }
        ));
     thread.Start();
}
3个回答

17

您正在将长时间操作放回UI线程中。让我评论您的示例:

Thread thread = new System.Threading.Thread( 
    new System.Threading.ThreadStart( 
        delegate() { 
            // here we are in the background thread

            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
                new Action(() => {
                    // here we are back in the UI thread
                    Thread.Sleep(15000);
                })); 
      } 
    )); 

所以,你应该像这样修改你的示例:

Thread thread = new System.Threading.Thread( 
    new System.Threading.ThreadStart( 
        delegate() { 
            // here we are in the background thread

            Thread.Sleep(15000);  // <-- do the long operation here

            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
                new Action(() => {
                    // here we are back in the UI thread

                    // do stuff here that needs to update the UI after the operation finished
                })); 
      } 
    )); 

正如其他人所提到的,使用BackgroundWorker类更加容易。以下是一个示例:

private void DoSomeAsyncWork()   
{   
    BackgroundWorker bw = new BackgroundWorker();

    bw.DoWork += (sender, args) => {
        // do your lengthy stuff here -- this will happen in a separate thread
        Thread.Sleep(15000);
    }

    bw.RunWorkerCompleted += (sender, args) => {
        if (args.Error != null)  // if an exception occurred during DoWork,
            MessageBox.Show(args.Error.ToString());  // do your error handling here

        // do any UI stuff after the long operation here
        ...
    }

    bw.RunWorkerAsync(); // start the background worker
}

4
通过使用 BeginInvoke 方法,您实际上正在 UI 线程上执行代码。
只有在从后台工作线程更新 UI 时才需要使用此方法。如果您仅使用如下代码,则无需使用此方法:
 Thread thread = new System.Threading.Thread(
     new System.Threading.ThreadStart(
      delegate()
      {
           Thread.Sleep(15000);
      }
    ));

我认为它会起作用。

但是,你没有触发“工作完成”事件,因此你没有办法知道线程何时(或者是否)已经完成。查看BackgroundWorker。它可以为你做很多重活,你只需要将你的代码插入DoWork方法中即可。


我正在尝试执行一些需要自己线程的代码,当我将这行代码直接放在第一个委托中(即您有Thread.Sleep(15000)的地方)时,我会收到一条消息,说该线程无法访问对象。这是消息:"调用线程无法访问此对象,因为不同的线程拥有它。" 显然,这是一个常见问题,但我正在努力解决这个问题,而不走使用后台工作器的路线。所以当我添加了第二个嵌套的BeginInvoke调用时,找到了该对象,但UI被阻塞了。 - fin
所以我想要的是一种方法,将似乎需要访问UI线程的逻辑放在新的后台线程上。总结一下,允许访问UI对象但在后台运行? - fin
@Finbar:你不能“授权访问”。UI操作 必须 在UI线程中完成。然而,你可以将UI操作分成小块,在后台线程中放置大循环,并在循环内调用 Dispatcher.BeginInvoke(...short UI operation...) 。这会导致UI被多次短暂阻塞,而不是一次长时间阻塞。 - Heinzi


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接