如何在后台线程中创建WPF控件?

11

我有一个方法可以创建后台线程来执行某些操作。在这个后台线程中,我创建了一个对象。但是当运行时创建此对象时,会给我一个异常:

  

调用线程必须是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 应用程序。


还有一个问题:后台工作器方法是否可以返回某个特定类型的值? - Polaris
2
在RunWorkerCompleted方法中检查e.Result属性。http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx。 - Amsakanna
8个回答

7

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。 - Polaris
我已经更新了我的答案,并添加了一些关于如何从工作线程中在UI线程中执行操作的注释。 - Dean Harding

3

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);
    }

2

目前没有人详细讨论单独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");
    }

因为现在我们将可视树保留在原始线程中。


0
尝试以下代码:
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
}

0
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);
    }; 
}

0
为了让你的代码正常工作,你必须通过调用Thread.SetApartmentState(ApartmentState.STA)加入一个STA COM公寓。由于BackgroundWorker可能正在使用一些共享线程池,加入特定的公寓可能会影响该线程池的其他用户,甚至可能失败,如果它已经被设置为例如MTA。即使所有这些都能够正常工作,你新创建的TreeView也将被锁定到此工作线程。你将无法在主UI线程中使用它。
如果你详细解释一下你的真实意图,你肯定会得到更好的帮助。

0

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


如果您想在一些时间间隔内更新您的用户界面,您可以使用ReportProgress方法的UserState参数来发送您的数据,并通过将e.UserState转换为所需类型,在ProgressChanged方法中使用它们。 - Amsakanna

0

请参阅此问题的答案: 如何在STA线程中运行某些内容?

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

thread.SetApartmentState(ApartmentState.STA);

这应该可以解决问题!


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