附加在另一个线程

时间:2015-11-25 18:26:52

标签: c# wpf multithreading async-await task

我们有一个必须使用DocumentViewer显示FixedDocument的WPF应用程序。

FixedDocument包含几个页面需要大约10秒才能创建,所以我们决定异步创建这个。

由于FixedDocument包含FixedPages,并且这些页面包含UserControl对象的实例,我们需要在STA Apartment上创建此文档。

我们遇到了将创建的FixedDocument附加到DocumentViewer的问题,因为它们位于不同的UI线程中。

以下是UI代码段:

<Grid Style="{StaticResource ContentRoot}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <Label Grid.Row="0" Content="Loading..." Visibility="{Binding LabelDocument.IsNotCompleted,
        Converter={StaticResource BooleanToVisibilityConverter}}"/>

    <DocumentViewer  Document="{Binding LabelDocument.Result}" Grid.Row="1"/>

    <Label Content="{Binding UrlByteCount.ErrorMessage}" Grid.Row="2" Background="Red"
        Visibility="{Binding LabelDocument.IsFaulted,
        Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>

比你有ViewModel:

public LabelPrintViewModel()
    {
        //....
        LabelDocument = new NotifyTaskCompletion<FixedDocument>(CreateLabels());
    }

 private Task<FixedDocument> CreateLabels()
    {
        var tcs = new TaskCompletionSource<FixedDocument>();
        Thread thread = new Thread(() =>
        {
            try
            {
                //Code to create the Fixed Pages goes here.


                FixedDocument fixedDocument = new FixedDocument();

                foreach (var page in pages)
                {
                    PageContent pageContent = new PageContent();

                    ((System.Windows.Markup.IAddChild)pageContent).AddChild(page);
                    fixedDocument.Pages.Add(pageContent);
                }
                tcs.SetResult(fixedDocument);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }

        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        return tcs.Task;
    }

当代码运行时,我得到了着名的“调用线程无法访问此对象,因为不同的线程拥有它”。

我尝试使用TaskScheduler.FromCurrentSynchronizationContext()来修复它,但没有成功:

return tcs.Task.ContinueWith<FixedDocument>(r=>
        {
            return r.Result;
        }, scheduler);

我理解错误的原因,但无法找到修复它的方法。使用调度程序并没有解决问题,因为我认为问题不在于调用代码的地方,而是我有两个需要通信的独立UI线程....

我该如何解决?

PS:DocumentViewer绑定方法是基于此great article执行的。

谢谢,

*更新* 我最终使用保存在临时文件上的XpsDocument作为中间媒体,因此DocumentViewer现在可以非常快速地打开它。将来,我希望找到一种方法来序列化FixedDocument,使其不会出现跨线程问题。

目前,我还在以编程方式创建包含每个FixedPage视觉的用户控件。

不知道为什么,但是第二次访问包含DocumentViewer的页面并生成了Document时,我无法再实例化我的自定义用户控件:

public async void OnNavigatedTo(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e)
    {
        LabelPrintViewModel vm = new LabelPrintViewModel();
        this.DataContext = vm;

        string s = await vm.LabelDocument.Task;

        XpsDocument doc = new XpsDocument(s, FileAccess.Read);
        docViewer.Document = doc.GetFixedDocumentSequence();

    } 

这次我将把代码放到创建页面上。第二次调用此线程时,应用程序挂起在LabelView构造函数调用上:

//Each item is a tinny ViewModel object to populate LabelView
                foreach (var item in _labelsToPrint)
                {
                    double pageWidth = 96 * 4.0;
                    double pageHeight = 96 * 2.12;

//The second time the calling page loads, the app hangs here.
                    LabelView lv = new LabelView(item);
                    lv.Width = pageWidth;
                    lv.Height = pageHeight;

                    FixedPage fp = new FixedPage();
                    fp.Width = pageWidth;
                    fp.Height = pageHeight;

                    fp.Children.Add(lv);

                    PageContent pageContent = new PageContent();
                    pageContent.Child = fp;

                    fixedDoc.Pages.Add(pageContent);
                }

                FileInfo tempFile = new FileInfo(System.IO.Path.GetTempPath() + DateTime.Now.Ticks.ToString() + ".xps");
                var paginator = fixedDoc.DocumentPaginator;
                var xpsDocument = new XpsDocument(tempFile.FullName, FileAccess.Write);
                var documentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
                documentWriter.Write(paginator);
                xpsDocument.Close();

                tcs.SetResult(tempFile.FullName);

如果我用TextBlock对象替换我的自定义控件,则不会出现问题。什么可能是错的?

更新

我缩小了自定义控件实例化问题,并隔离了使应用程序挂起的内容。 XAML包含对本地化机制(http://wpflocalizeextension.codeplex.com)的引用。有一些UI元素,如:

<TextBlock Text="{lex:Loc Description}" Style="{StaticResource labelTextBox}"/>

如果我在viewModel中进行本地化,问题就解决了。

0 个答案:

没有答案