none
ワーカースレッドでFixedDocumentを作成して、UIに表示する RRS feed

  • 質問


  • こんにちは
    件名のとおり、別のスレッドでドキュメントを作成してWindowにあるDocumentViewerに表示したいのですが、上手くいきません。

    ドキュメントは、Gridで作成しています。
    重たい処理だったので、別スレッドで作成してみたいと思い仮に実装してみたのですが何が問題なのか良くわかりません。
    エラーメッセージは下記のとおりです。

    追加情報:このオブジェクトは別のスレッドに所有されているため、呼び出しスレッドはこのオブジェクトにアクセスできません。

    以下の場合は、2行目の場合はViewerが別スレッドだからダメで、3行目の場合はdocが別スレッドだからダメということになるのでしょうか。

          // ERROR
                //Viewer.Document = doc;
                //dispatcher.Invoke(new Action(() => { Viewer.Document = doc; }));

    分かる方いましたら、ぜひ教えてください。
    よろしくお願いします。

    以下、コード。

    private void Button_Click(object sender, RoutedEventArgs e)
            {
                var thread = new Thread(Work) { Name = "Document Create Worker", IsBackground = true };
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
    
                Console.WriteLine("Total Memory = {0} KB", GC.GetTotalMemory(true) / 1024);
            }
    
            private void Work()
            {
                var dispatcher = Application.Current.Dispatcher;
    
                var doc = CreateDocument();
    
                // ERROR
                //Viewer.Document = doc;
                //dispatcher.Invoke(new Action(() => { Viewer.Document = doc; }));
            }
    
            private FixedDocument CreateDocument()
            {
                var document = new FixedDocument();
                var pageContent = new PageContent();
                var page = new FixedPage
                {
                    Width = 800,
                    Height = 600,
                };
    
                var printGrid = new Grid();
                printGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
                printGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
    
                // 重たい処理
                var label = new Label { Content = "Test Content Data." };
                label.SetValue(Grid.ColumnProperty, 0);
                label.SetValue(Grid.RowProperty, 0);
                printGrid.Children.Add(label);
    
                page.Children.Add(printGrid);
                pageContent.Child = page;
                document.Pages.Add(pageContent);
    
                // UI要素をバックグラウンド処理で作成するとメモリリークが発生するため、明示的にシャットダウンを指示する
                Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.SystemIdle);
                Dispatcher.Run();
    
                return document;
            }


    サンプルのプロジェクトファイル
    http://1drv.ms/1DYsEIC



    • 編集済み ichiethel 2015年4月14日 9:12
    2015年4月14日 9:11

回答

  • 簡単なのは

    private void Work()
    {
        var dispatcher = Application.Current.Dispatcher;
    
        System.IO.MemoryStream ms= CreateDocument();
        dispatcher.Invoke(new Action(() =>
        {
            var doc=(FixedDocument) System.Windows.Markup.XamlReader.Load(ms);
            viewer.Document = doc;
        }));
    }
    
    private System.IO.MemoryStream CreateDocument()
    {
        var document = new FixedDocument();
    
      //省略(シャットダウンしない?)
    
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        System.Windows.Markup.XamlWriter.Save(document, ms);
        ms.Position = 0;
        return ms;
    }

    のように一度保存して、メインスレッドで再生させるとか。

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Work(null);
    }
    
    private void Work(IEnumerator<FixedDocument> coworker)
    {
        Action<IEnumerator<FixedDocument>> act = Work;
    
        if (coworker == null)
        {
            coworker = CreatePages().GetEnumerator();
            Dispatcher.BeginInvoke(act, DispatcherPriority.Background, coworker);
            return;
        }
        else if (coworker.MoveNext())
        {
            var doc = coworker.Current;
            if (doc == null)
            {
                Dispatcher.BeginInvoke(act, DispatcherPriority.Background, coworker);
                return;
            }
            else
            {
                viewer.Document = doc;
            }
        }
        coworker.Dispose();
    }
    private void DoBackground(IEnumerator<FixedDocument> coworker)
    {
        Action<IEnumerator<FixedDocument>> act = Work;
        Dispatcher.BeginInvoke(act, DispatcherPriority.Background, coworker);
    }
    
    private IEnumerable<FixedDocument> CreatePages()
    {
        var document = new FixedDocument();
    
        for (int i = 0; i < 100; i++)
        {
            var pageContent = new PageContent();
            var page = new FixedPage() { Width = 800, Height = 600 };
    
            var printGrid = new Grid();
            printGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
            printGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
    
            // 重たい処理
            var label = new Label { Content = "Test Content Data." };
            label.SetValue(Grid.ColumnProperty, 0);
            label.SetValue(Grid.RowProperty, 0);
            printGrid.Children.Add(label);
    
            System.Threading.Thread.Sleep(100);
    
            page.Children.Add(printGrid);
            pageContent.Child = page;
            document.Pages.Add(pageContent);
    
            yield return null;
        }
    
        yield return document;
    }
    のように、ドキュメント構築を細かい処理単位に分割して、同じDispatcherのBackgroud優先度で処理を進めるとか。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2015年4月14日 10:31
    • 回答としてマーク ichiethel 2015年4月15日 0:47
    2015年4月14日 9:52

すべての返信

  • バックグラウンドスレッドでGridを作成するのは明らかに無理があると思います。

    ※別スレッドで作成して扱えるのは、Freezableな一部のコンポーネントとかだけだと思います(Freezeする必要あると思いますが)。

    --追記

    あああ、WPF(xaml)だとこういうこともできるんですね…>gekkaさんの回答

    • 編集済み なちゃ 2015年4月14日 9:56
    2015年4月14日 9:43
  • 簡単なのは

    private void Work()
    {
        var dispatcher = Application.Current.Dispatcher;
    
        System.IO.MemoryStream ms= CreateDocument();
        dispatcher.Invoke(new Action(() =>
        {
            var doc=(FixedDocument) System.Windows.Markup.XamlReader.Load(ms);
            viewer.Document = doc;
        }));
    }
    
    private System.IO.MemoryStream CreateDocument()
    {
        var document = new FixedDocument();
    
      //省略(シャットダウンしない?)
    
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        System.Windows.Markup.XamlWriter.Save(document, ms);
        ms.Position = 0;
        return ms;
    }

    のように一度保存して、メインスレッドで再生させるとか。

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Work(null);
    }
    
    private void Work(IEnumerator<FixedDocument> coworker)
    {
        Action<IEnumerator<FixedDocument>> act = Work;
    
        if (coworker == null)
        {
            coworker = CreatePages().GetEnumerator();
            Dispatcher.BeginInvoke(act, DispatcherPriority.Background, coworker);
            return;
        }
        else if (coworker.MoveNext())
        {
            var doc = coworker.Current;
            if (doc == null)
            {
                Dispatcher.BeginInvoke(act, DispatcherPriority.Background, coworker);
                return;
            }
            else
            {
                viewer.Document = doc;
            }
        }
        coworker.Dispose();
    }
    private void DoBackground(IEnumerator<FixedDocument> coworker)
    {
        Action<IEnumerator<FixedDocument>> act = Work;
        Dispatcher.BeginInvoke(act, DispatcherPriority.Background, coworker);
    }
    
    private IEnumerable<FixedDocument> CreatePages()
    {
        var document = new FixedDocument();
    
        for (int i = 0; i < 100; i++)
        {
            var pageContent = new PageContent();
            var page = new FixedPage() { Width = 800, Height = 600 };
    
            var printGrid = new Grid();
            printGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
            printGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
    
            // 重たい処理
            var label = new Label { Content = "Test Content Data." };
            label.SetValue(Grid.ColumnProperty, 0);
            label.SetValue(Grid.RowProperty, 0);
            printGrid.Children.Add(label);
    
            System.Threading.Thread.Sleep(100);
    
            page.Children.Add(printGrid);
            pageContent.Child = page;
            document.Pages.Add(pageContent);
    
            yield return null;
        }
    
        yield return document;
    }
    のように、ドキュメント構築を細かい処理単位に分割して、同じDispatcherのBackgroud優先度で処理を進めるとか。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2015年4月14日 10:31
    • 回答としてマーク ichiethel 2015年4月15日 0:47
    2015年4月14日 9:52
  • なちゃさん、gekkaさん
    回答ありがとうございました。

    gekkaさんに示して頂いた、簡単な解決方法でひとまず解決することができました。
    これで様子を見てみることにします。不満があるようなら処理単位の分割なども検討してみたいと思います。

    やはり基本的にはFreesableできるコンポーネントを処理するのが基本だったんですね。
    そのあたりも知ることができて良かったです。

    private void Button_Click(object sender, RoutedEventArgs e)
            {
                var thread = new Thread(Work) { Name = "Document Create Worker", IsBackground = true };
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
    
                Console.WriteLine("Total Memory = {0} KB", GC.GetTotalMemory(true) / 1024);
            }
    
            private void Work()
            {
                var dispatcher = Application.Current.Dispatcher;
    
                var doc = CreateDocument();
    
                var ms = new MemoryStream();
                XamlWriter.Save(doc, ms);
                ms.Position = 0;
    
                //
                //Viewer.Document = doc;
                dispatcher.Invoke(new Action(() => { Viewer.Document = XamlReader.Load(ms) as FixedDocument; }));
            }
    
            private FixedDocument CreateDocument()
            {
                var document = new FixedDocument();
                var pageContent = new PageContent();
                var page = new FixedPage
                {
                    Width = 800,
                    Height = 600,
                };
    
                var printGrid = new Grid();
                printGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
                printGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
    
                // 重たい処理
                var label = new Label { Content = "Test Content Data." };
                label.SetValue(Grid.ColumnProperty, 0);
                label.SetValue(Grid.RowProperty, 0);
                printGrid.Children.Add(label);
    
                page.Children.Add(printGrid);
                pageContent.Child = page;
                document.Pages.Add(pageContent);
    
                // UI要素をバックグラウンド処理で作成するとメモリリークが発生するため、明示的にシャットダウンを指示する
                Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.SystemIdle);
                Dispatcher.Run();
    
                return document;
            }
    ※公開していたプログラムも修正しました。

    2015年4月15日 1:07