Как я могу создать элементы управления WPF в фоновом потоке?

У меня есть метод, который создает фоновый поток, чтобы сделать некоторые действия. В этом фоновом потоке я создаю объект. Но этот объект при создании во время выполнения дает мне исключение:

Вызывающий поток должен быть STA, потому что для этого требуются многие компоненты пользовательского интерфейса.

Я знаю, что я должен использовать диспетчер, чтобы что-то отразить в пользовательском интерфейсе. Но в этом случае я просто создаю объект и не выполняю его с пользовательским интерфейсом. Это мой код:

    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

Ответ 1

TreeView является элементом управления пользовательским интерфейсом. Вы можете создавать и управлять элементами управления пользовательского интерфейса в потоке пользовательского интерфейса, поэтому то, что вы пытаетесь сделать, невозможно.

Что вы хотите сделать, это выполнить всю трудоемкую работу в фоновом потоке, а затем "перезвонить" в поток пользовательского интерфейса для управления пользовательским интерфейсом. Это на самом деле довольно просто:

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 (он сверху моей головы), но там вы все равно...

Ответ 2

НТН:

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

конечно, получим a 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

Чтобы ваш код просто работал, вы должны присоединиться к квартире STA COM, вызвав Thread.SetApartmentState(ApartmentState.STA). Поскольку BackgroundWorker, вероятно, использует некоторый пул общих потоков, присоединение к конкретной квартире может повлиять на других пользователей этого пула потоков или может даже потерпеть неудачу, если оно уже было установлено, например. MTA раньше. Даже если все это сработает, ваш вновь созданный TreeView будет заблокирован для этого рабочего потока. Вы не сможете использовать его в своем основном потоке пользовательского интерфейса.

Если вы более подробно объясните свои истинные намерения, вы, несомненно, получите лучшую помощь.

Ответ 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

Я решил свою проблему. Я просто использовал свойство e.Result метода RunWorkerCompleted. Я получаю данные в фоновом потоке, а затем использую эти данные, когда поток завершен. Спасибо всем органам за полезные методы. Особая благодарность Veer дать рекомендацию о свойстве e.Result.

Ответ 8

См. ответ на этот вопрос: Как запустить что-то в потоке STA?

Когда вы определяете свой поток, установите ApartmentState в STA:

thread.SetApartmentState(ApartmentState.STA);

Это должно сделать трюк!