トップ回答者
ワーカースレッドでFixedDocumentを作成して、UIに表示する

質問
-
こんにちは
件名のとおり、別のスレッドでドキュメントを作成して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
回答
-
簡単なのは
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!)
すべての返信
-
簡単なのは
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!)
-
なちゃさん、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; }
※公開していたプログラムも修正しました。