Генерация и передача сложного контента в поток GUI в WPF / C # - PullRequest
3 голосов
/ 28 августа 2010

Я знаю и использую метод xxx.Dispatcher.Invoke (), чтобы получить фоновый поток для манипулирования элементами GUI. Я думаю, что столкнулся с чем-то похожим, но немного другим, где я хочу, чтобы долго выполняемая фоновая задача создала дерево объектов, и когда это было сделано, передайте его в графический интерфейс для отображения.

Попытка сделать это приводит к InvalidOperationException, «из-за вызывающего потока не может получить доступ к этому объекту, потому что другой поток владеет им». Любопытно, что это не происходит с простым шрифтом.

Вот пример кода, который демонстрирует тривиальный случай, когда выбрасывается исключение. Есть идеи, как обойти это? Я почти уверен, что проблема в том, что фоновый поток владеет созданным на заводе объектом, и что поток графического интерфейса переднего плана не может стать владельцем, хотя он работает для более простых типов систем.

private void button1_Click(object sender, RoutedEventArgs e) 
{  
   // These two objects are created on the GUI thread
   String abc = "ABC";  
   Paragraph p = new Paragraph();

   BackgroundWorker bgw = new BackgroundWorker();

   // These two variables are place holders to give scoping access
   String def = null;
   Run r = null;

   // Initialize the place holders with objects created on the background thread
   bgw.DoWork += (s1,e2) =>
     {
       def = "DEF";
       r = new Run("blah");
     };

   // When the background is done, use the factory objects with the GUI
   bgw.RunWorkerCompleted += (s2,e2) =>
     {
        abc = abc + def;         // WORKS: I suspect there's a new object
        Console.WriteLine(abc);  // Console emits 'ABCDEF'

        List<String> l = new List<String>();  // How about stuffing it in a container?
        l.Add(def);                           // WORKS: l has a reference to def

        // BUT THIS FAILS.
        p.Inlines.Add(r);  // Calling thread cannot access this object
     };

   bgw.RunWorkerAsync();
}

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

Как фоновый работник может выступать в качестве фабрики объектов и передавать контент в основной поток?

Спасибо!

Ответы [ 2 ]

5 голосов
/ 28 августа 2010

Вы пытаетесь создать Run в фоновом потоке, но Run является FrameworkContentElement, который наследуется от DispatcherObject и, таким образом, привязан к потоку, который его создал.

1 голос
/ 28 августа 2010

Как сказал Франци, Run - это DispatcherObject, поэтому его можно обновлять только в потоке, который его создал.Код должен выполняться, если он вызывает Dispatch.Invoke или Dispatcher.BeginInvoke, например:

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        var button = sender as Button;

        string abc = "ABC";
        var p = new Paragraph();

        var bgw = new BackgroundWorker();

        String def = null;
        Run r = null;

        bgw.DoWork += (s1, e2) =>
          {
              def = "DEF";
              button.Dispatcher.BeginInvoke(new Action(delegate{r = new Run("blah");}));
          };

        bgw.RunWorkerCompleted += (s2, e2) =>
          {
              abc = abc + def;
              Console.WriteLine(abc); 
              var l = new List<String> { def };
              p.Inlines.Add(r);  // Calling thread can now access this object because 
                                 // it was created on the same thread that is updating it.
          };

        bgw.RunWorkerAsync();
    }
...