【问题标题】:How can I create WPF controls in a background thread?如何在后台线程中创建 WPF 控件?
【发布时间】:2010-05-04 06:44:12
【问题描述】:

我有创建后台线程来执行某些操作的方法。在这个后台线程中,我创建了对象。但是这个对象在运行时创建时给了我一个例外:

调用线程必须是 STA,因为很多 UI 组件都需要这个。

我知道我必须使用 Dispatcher 将某些内容反映到 UI。但在这种情况下,我只是创建一个对象而不与 UI 交互。这是我的代码:

    public void SomeMethod()
      {
         BackgroundWorker worker = new BackgroundWorker();
         worker.DoWork += new DoWorkEventHandler(Background_Method);
         worker.RunWorkerAsync();
      }

   void Background_Method(object sender, DoWorkEventArgs e)
      {
         TreeView tv = new TreeView();
      }

如何在后台线程中创建对象?

我使用 WPF 应用程序

【问题讨论】:

  • 还有一个问题:Background Worker 方法是否可以返回一些特定类型的值?
  • 检查 RunWorkerCompleted 方法中的 e.Result 属性。 msdn.microsoft.com/en-us/library/….

标签: c# wpf multithreading backgroundworker


【解决方案1】:

TreeView 是一个 UI 控件。您只能在 UI 线程上创建和操作 UI 控件,因此您尝试做的事情是不可能的。

您要做的是在后台线程上完成所有耗时的工作,然后“回调”到 UI 线程来操作 UI。这其实很简单:

void Background_Method(object sender, DoWorkEventArgs e)
{
    // ... time consuming stuff...

    // call back to the window to do the UI-manipulation
    this.BeginInvoke(new MethodInvoker(delegate {
        TreeView tv = new TreeView();
        // etc, manipulate
    }));
}

BeginInvoke 的语法可能有误(我想不通),但不管怎样……

【讨论】:

  • 我从 Web 服务和运行时获取了一些数据,为此花费了很多时间。这就是为什么我想在后台获取数据并在数据准备好时生成我的 treeView。
  • 我已经用更多关于如何从工作线程在 UI 线程中执行操作的 cmets 更新了我的答案。
【解决方案2】:

HTH:

    void Background_Method(object sender, DoWorkEventArgs e)
    {
        // Time Consuming operations without using UI elements
        // Result of timeconsuming operations
        var result = new object();
        App.Current.Dispatcher.Invoke(new Action<object>((res) =>
            {
                // Working with UI
                TreeView tv = new TreeView();
            }), result);
    }

【讨论】:

    【解决方案3】:

    没有人详细讨论单独的 STA 线程的情况(即使概念完全相同)。

    让我们想象一下在单击按钮时添加一个简单的选项卡控件

        private void button_Click(object sender, RoutedEventArgs e)
        {
            TabItem newTab = new TabItem() { Header = "New Tab" };
            tabMain.Items.Add(newTab);
        }
    

    如果我们把它移到另一个 STA 线程

        private void button_Click(object sender, RoutedEventArgs e)
        {
            Thread newThread = new Thread(new ThreadStart(ThreadStartingPoint));
            newThread.SetApartmentState(ApartmentState.STA);
            newThread.IsBackground = true;
            newThread.Start();
        }
        private void ThreadStartingPoint()
        {
            TabItem newTab = new TabItem() { Header = "New Tab" };
            tabMain.Items.Add(newTab);
        }
    

    我们当然会收到System.InvalidOperationException

    现在,如果我们添加控件会发生什么

        private void AddToParent(string header)
        {
            TabItem newTab = new TabItem() { Header = header };
            tabMain.Items.Add(newTab);
        }
    

    使用委托方法?

        public void DelegateMethod(string header)
        {
            tabMain.Dispatcher.BeginInvoke(
                    new Action(() => {
                        this.AddToParent(header);
                    }), null);
        }
    

    如果你调用它,它确实有效

        private void button_Click(object sender, RoutedEventArgs e)
        {
            Thread newThread = new Thread(new ThreadStart(ThreadStartingPoint));
            newThread.SetApartmentState(ApartmentState.STA);
            newThread.IsBackground = true;
            newThread.Start();
        }
        private void ThreadStartingPoint()
        {
            DelegateMethod("new tab");
        }
    

    因为当然现在我们将可视化树保留在同一个原始线程中。

    【讨论】:

      【解决方案4】:

      为了使您的代码简单地工作,您必须通过调用Thread.SetApartmentState(ApartmentState.STA) 加入STA COM 公寓。由于BackgroundWorker 可能正在使用一些共享线程池,加入特定单元可能会影响该线程池的其他用户,或者如果它已经设置为例如可能会失败,例如MTA 之前。即使一切顺利,您新创建的TreeView 也会被锁定到该工作线程。您将无法在主 UI 线程中使用它。

      如果你把你的真实意图解释得更详细一点,你肯定会得到更好的帮助。

      【讨论】:

        【解决方案5】:

        试试下面的代码:

        public void SomeMethod() 
        { 
        
        System.ComponentModel.BackgroundWorker myWorker = new  System.ComponentModel.BackgroundWorker();
        
        myWorker.DoWork += myWorker_DoWork;
        
        myWorker.RunWorkerAsync();
        
        }
        
        private void myWorker_DoWork(object sender,
           System.ComponentModel.DoWorkEventArgs e)
        {
           // Do time-consuming work here
        }
        

        【讨论】:

          【解决方案6】:
          void Background_Method(object sender, DoWorkEventArgs e) 
          { 
              TreeView tv = new TreeView(); 
              // Generate your TreeView here
              UIDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
              { 
                  someContainer.Children.Add(tv);
              }; 
          }
          

          【讨论】:

            【解决方案7】:

            我解决了我的问题。我刚刚使用了 RunWorkerCompleted 方法的 e.Result 属性。我在后台线程中获取数据,然后在线程完成时使用这些数据。感谢每个人提供有用的方法。特别感谢 Veer 对e.Result property 的推荐。

            【讨论】:

            • 如果您想每隔一段时间更新您的 UI,您可以使用 ReportProgress 方法的 UserState 参数发送您的数据,并通过将 e.UserState 强制转换为所需的类型,在您的 ProgressChanged 方法中使用它们。跨度>
            【解决方案8】:

            查看这个问题的答案: How to run something in the STA thread?

            定义线程时,将 ApartmentState 设置为 STA:

            thread.SetApartmentState(ApartmentState.STA);
            

            这应该可以解决问题!

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-10-26
              • 2012-01-08
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多