WPF RichTextBox 文档创建线程问题

发布于 2024-10-30 23:10:39 字数 2992 浏览 1 评论 0原文

我在线程方面的 RTB 和文档生成方面遇到了一些问题。

当 RTB 上触发 TextChanged 事件时,会创建一个新的标题,并且文档生成将被卸载到此。这可能需要几秒钟的时间,并且会阻塞调用,因此它确实需要位于另一个线程上才能保持 UI 响应。

我遇到的问题是当我尝试将新生成的文档添加到 RTB 的 Document 属性时出现异常。 (调用线程无法访问此对象,因为不同的线程拥有它。)这不是由于忘记使用 Dispatcher.Invoke,因为这就是生成异常的地方,但因为我是在 UI 线程以外的线程上创建 FlowDocument/Paragraph/Run 实例(我认为??)。

有没有办法实现我在这里寻找的东西?

更新

    private void rtbQuery_TextChanged(object sender, TextChangedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Requires update; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);

        backgroundWorker.RunWorkerAsync();
    }

    private void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Generating; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);

        DocumentGenerator dgen = new DocumentGenerator();
        string queryText = getQueryText();

        e.Result = dgen.GenerateDocument(queryText);
    }

    private void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Assigning; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);

        FlowDocument doc = (FlowDocument)e.Result;
        txtQuery.Document = doc; // ! The calling thread cannot access this object because a different thread owns it
    }

>Requires update; on thread:9
>Generating; on thread:10
>Assigning; on thread:9

更新 #2 - 一个解决方案

(某种)

因此,正如 @Jon Mitchell 指出的那样,我无法使用在另一个线程上创建的对象来更新 UI 线程上的 RTB。有一个非常简单的解决方案,需要最少的代码更改来解决这个问题,我将其发布以节省未来的人们的麻烦。简而言之,对象图是在另一个线程上创建的,然后转换为 XAML。然后,UI 线程在其自己的线程中将此 XAML 转换回对象图,一切正常。

    private void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        DocumentGenerator dgen = new DocumentGenerator();
        string queryText = getQueryText();
        
        dgen.GenerateDocument(queryText); // start generation
        e.Result = dgen; // note, i'm passing the generator, not the document
    }

    private void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        DocumentGenerator dgen = (DocumentGenerator)e.Result;
        txtQuery.Document = dgen.GetFlowDocument(); 
    }

在 DocumentGenerator 类中

    public void GenerateDocument(string data)
    {
        ... // build up the document DOM

        // return documentDOM; // used to return the generated item here.
        documentXAML = System.Windows.Markup.XamlWriter.Save(documentDOM); // serialize the DOM to XAML
    }
    public FlowDocument GetDocument()
    {
        object result = System.Windows.Markup.XamlReader.Parse(documentXAML); // build DOM from XAML
        return (FlowDocument)result;
    }

I'm having a bit of an issue with a RTB and document generation in regards to threads.

When the TextChanged event fires on the RTB, a new thead is created, and the document generation is offloaded to this. This can take a couple of seconds, with blocking calls, so it really needs to be on another thread to keep the UI responsive.

The problem I'm having is an exception when I try to add the newly generated document to the Document property of the RTB. ( The calling thread cannot access this object because a different thread owns it.) This is not due to forgetting to use Dispatcher.Invoke, as thats where the exception is generated, but because I'm creating the FlowDocument/Paragraph/Run instances on a thread other than the UI thread(I think??).

Is there a way to achieve what I'm looking for here?

Update

    private void rtbQuery_TextChanged(object sender, TextChangedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Requires update; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);

        backgroundWorker.RunWorkerAsync();
    }

    private void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Generating; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);

        DocumentGenerator dgen = new DocumentGenerator();
        string queryText = getQueryText();

        e.Result = dgen.GenerateDocument(queryText);
    }

    private void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Assigning; on thread:" + System.Threading.Thread.CurrentThread.ManagedThreadId);

        FlowDocument doc = (FlowDocument)e.Result;
        txtQuery.Document = doc; // ! The calling thread cannot access this object because a different thread owns it
    }

>Requires update; on thread:9
>Generating; on thread:10
>Assigning; on thread:9

Update #2 - A solution

(of sorts)

So, as @Jon Mitchell pointed out, I cant update my RTB on the UI thread, with an object created on another thread. There is a very simple solution, that requires minimal code change, to work around this though, and i'm posting it up to save future people the hassle. Briefly explained, an object graph is created on the other thread, and then converted to XAML. The UI thread then converts this XAML back to the object graph, in its own thread, and everything works a-ok.

    private void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        DocumentGenerator dgen = new DocumentGenerator();
        string queryText = getQueryText();
        
        dgen.GenerateDocument(queryText); // start generation
        e.Result = dgen; // note, i'm passing the generator, not the document
    }

    private void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        DocumentGenerator dgen = (DocumentGenerator)e.Result;
        txtQuery.Document = dgen.GetFlowDocument(); 
    }

In DocumentGenerator class

    public void GenerateDocument(string data)
    {
        ... // build up the document DOM

        // return documentDOM; // used to return the generated item here.
        documentXAML = System.Windows.Markup.XamlWriter.Save(documentDOM); // serialize the DOM to XAML
    }
    public FlowDocument GetDocument()
    {
        object result = System.Windows.Markup.XamlReader.Parse(documentXAML); // build DOM from XAML
        return (FlowDocument)result;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

樱&纷飞 2024-11-06 23:10:39

我认为问题是因为 FlowDocument 是一个 DependencyObject ,它不可冻结,因此不能在一个线程上创建然后在另一个线程上使用。我认为这是因为当在另一个线程上创建 FlowDocument 时,它对于 RTB 有一个不同的调度程序。

可以在AAron 的博客上找到解决方法:

FlowDocument doc = new FlowDocument(new Paragraph(new Run("hi")));
System.IO.MemoryStream 流 = new System.IO.MemoryStream();
XamlWriter.Save(文档,流);
流.Position = 0;
      
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ZeroDelegate)delegate ()
{
    flowDoc = (FlowDocument)XamlReader.Load(流);
});

I think the issue is because the FlowDocument is a DependencyObject which isn't freezable and therefore can't be created on one thread and then used on a different one. I think its because when the FlowDocument is created on the other thread it has a different dispatcher, to the RTB.

A workaround for this can be found on AAron's blog:

FlowDocument doc = new FlowDocument(new Paragraph(new Run("hi")));
System.IO.MemoryStream stream = new System.IO.MemoryStream();
XamlWriter.Save(doc, stream);
stream.Position = 0;
      
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ZeroDelegate)delegate ()
{
    flowDoc = (FlowDocument)XamlReader.Load(stream);
});
追星践月 2024-11-06 23:10:39

试试这个:

    private void backgroundWorker_RunWorkerCompleted(object sender,
        System.ComponentModel.RunWorkerCompletedEventArgs e)    
    {        
        System.Diagnostics.Debug.WriteLine(
            "Assigning; on thread:" + 
            System.Threading.Thread.CurrentThread.ManagedThreadId);

        Dispatcher.BeginInvoke(new Action(
            delegate()
            {
                FlowDocument doc = (FlowDocument)e.Result;
                txtQuery.Document = doc;
            }
        ), null);
    }

try this:

    private void backgroundWorker_RunWorkerCompleted(object sender,
        System.ComponentModel.RunWorkerCompletedEventArgs e)    
    {        
        System.Diagnostics.Debug.WriteLine(
            "Assigning; on thread:" + 
            System.Threading.Thread.CurrentThread.ManagedThreadId);

        Dispatcher.BeginInvoke(new Action(
            delegate()
            {
                FlowDocument doc = (FlowDocument)e.Result;
                txtQuery.Document = doc;
            }
        ), null);
    }
国际总奸 2024-11-06 23:10:39

我一直在寻找同一问题的解决方案,并最终提出了替代解决方案。

技巧是:

  1. 在后台线程上创建 FlowDocument 和所有内部元素
  2. 使用反射更改 FlowDocument 的调度程序和所有内部元素(可能包括 UI 调度程序的样式和资源)。
  3. 在 UI 中使用 FlowDocument

该解决方案将所有工作保留在后台线程中,并且不涉及后台线程序列化和 UI 线程反序列化,这将进一步提高响应能力。

请在我的博客文章中找到代码。

[编辑] 关于此解决方案的一个非常需要的说明:它基本上是一种 hack,尚未解决在 FlowDocument 中处理图像的问题,因为图像需要在前台(UI)线程上处理,这似乎是 .网本身。

对于我从事的需要生成前台报告的项目,我决定在后台线程上处理尽可能多的内容,并牺牲一些 GUI 响应能力来构建 FlowDocument(大约占总报告准备时间的 20%)。

I was looking for a solution to the same problem and eventually came up with an alternative solution.

The trick is to:

  1. Create FlowDocument and all inner elements on the background thread
  2. Using reflection change the Dispatcher of the FlowDocument and all inner elements possibly including styles and resources to the UI dispatcher.
  3. Use the FlowDocument in the UI

The solution keeps all the work in the background thread and does not involve background thread serialization and UI thread deserialization which will improve responsiveness even more.

Please find the code in my blog post.

[Edit] A much needed note about this solution: it basically is a hack and one has yet to solve an issue of handling images in FlowDocument as images need to be handled on a foreground (UI) thread which seems to be a limitation of .Net itself.

For the project I worked on which required foreground report generation I decided to handle as much as possible on background thread and sacrifice some GUI responsiveness for a time of building FlowDocument (about 20% of total report preparation time).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文