2011-05-20 150 views
2

访问一个WPF的FlowDocument在后台在后台进程中

我的问题涉及访问UI对象,在WPF背景访问一个WPF的FlowDocument。我看过几十个示例应用程序,所有这些应用程序都很简单,易于使用,其中95%告诉你如何显示进度条。这不是我想要的......

我的问题是这样的:我想通过访问RichTextBox中的FlowDocument来执行长任务(或许多长任务)。确切的任务在这里没有关系,但是一个例子可能是扫描整个文档,并计算特定单词出现的次数,或者有多少红色字符......。在长文档中,这些可能是相当耗时的任务,并且如果在前台完成,将会大大限制UI并使其无响应。我只想解析FlowDocument;我不想对它做任何更改。

所以这就是我想要做的事情。显而易见的解决方案是在后台进行,但问题是......怎么做?我已经实现了似乎是一个答案,但它只是对我来说“不适合”,这就是为什么我在这里寻求帮助。

我跟随“解决方案”

我的“解决方案”使用它调用UI对象的调度,以确保正确的线程访问一个BackgroundWorker ......和“出现”它做的工作。但是这样做吗?.............. 我已经大大简化了我的“解决方案”,使它变得简单(我希望)遵循我所做的...。

WithEvents worker As BackgroundWorker 
Private Delegate Sub DelegateSub() 
Private theDocument As FlowDocument 

''' <summary> 
''' Triggers the background task. Can call from anywhere in main code blocks 
''' </summary> 
Private Sub StartTheBackgroundTask() 

    worker = New BackgroundWorker 
    worker.RunWorkerAsync() 

End Sub 

''' <summary> 
''' In the background, hands the job over to the UI object's Dispatcher 
''' </summary> 
Private Sub HandleWorkerDoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork 

    Dim priority As System.Windows.Threading.DispatcherPriority 
    Dim theLongRunningTask As DelegateSub 

    '(1) Define a delegate for the Dispatcher to work with 
    theLongRunningTask = New DelegateSub(AddressOf DoTheTimeConsumingTask) 

    '(2) Set Dispatcher priority as required 
    priority = System.Windows.Threading.DispatcherPriority.Background 

    '(3) Add the job to the FlowDocument's Dispatcher's tasks 
    theDocument.Dispatcher.BeginInvoke(theLongRunningTask, priority) 

End Sub 

''' <summary> 
''' Sub whose logic accesses, but does not change, the UI object 
''' </summary> 
Private Sub DoTheTimeConsumingTask() 

    'For example...... 

    For Each bl As Block In theDocument.Blocks 

     '......do something 

    Next 

End Sub 

虽然这似乎工作,因为我看到它的问题是,除了触发与BackgroundWorker的任务,几乎所有的长期运行的任务由UI对象的调度处理。所以BackgroundWorker实际上并没有做任何工作。这是我关心的部分;我看不到我如何得到什么,如果分派器捆绑起来做所有的工作

选项2

因此,它似乎更符合逻辑,我认为我会更好“这扭曲“并将Dispatcher的委托设置为指向实例化并启动BackGroundWorker的子(我认为Dispatcher线程将拥有BackgroundWorker的线程),并执行BackgroundWorker的DoWork事件中的所有工作。这种“感觉”是正确的...。

所以我想这一点,而不是:

WithEvents worker As BackgroundWorker 
Private Delegate Sub DelegateSub() 
Private theDocument As FlowDocument 

''' <summary> 
''' Triggers the background task. Can call from anywhere in main code blocks 
''' </summary> 
Private Sub StartTheBackgroundTask() 

    Dim priority As System.Windows.Threading.DispatcherPriority 
    Dim theTask As DelegateSub 

    '(1) Define a delegate for the Dispatcher to work with 
    theTask = New DelegateSub(AddressOf RunWorker) 

    '(2) Set Dispatcher priority as required 
    priority = System.Windows.Threading.DispatcherPriority.Normal 

    '(3) Add the job to the Dispatcher's tasks 
    theDocument.Dispatcher.BeginInvoke(theTask, priority) 

End Sub 

''' <summary> 
''' Creates and starts a new BackGroundWorker object 
''' </summary> 
Private Sub RunWorker() 

    Worker = New BackgroundWorker 
    Worker.RunWorkerAsync() 

End Sub 

''' <summary> 
''' Does the long task in the DoWork event 
''' </summary> 
Private Sub HandleWorkerDoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork 

    DoTheTimeConsumingTask() 

End Sub 

''' <summary> 
''' Sub whose logic accesses, but does not change, the UI object 
''' </summary> 
Private Sub DoTheTimeConsumingTask() 

    'For example...... 

    For Each bl As Block In theDocument.Blocks 

     '......do something 

    Next 

End Sub 

对我来说,所有的似乎更符合逻辑。我推测Dispatcher将拥有BackgroundWorker,而BackgroundWorker将继续完成所有的长时间工作,并且所有事情都将在UI线程上进行。那么... ...对于逻辑思考来说太多了......(通常对于WPF来说是致命的!)......它不会。它与通常的“不同线程”错误崩溃。所以,第二个想法似乎是一个更加优雅的解决方案,原来是一个失败者!

我的问题则是:

  1. 是我的“解决方案”的解决方案,或不?
  2. 我哪里错了?
  3. 怎样才能改善“解决方案”,使调度员不被长时间的工作束缚......这正是我试图避免的情况?

还有一个问题。请注意,我必须使用FlowDocument的调度程序来完成这项工作。如果我使用System.Windows.Threading.Dispatcher.CurrentDispatcher来代替,那么委托子(DoTheTimeConsumingTask)不会被调用 - 所有意图和目的都不会发生。有人可以解释为什么不,请吗?

我还没有来找你作为第一停靠港。我已经尝试了几十种选择,并且还没有发现任何感觉完全正确的东西(除了我的第二个选项,它不起作用),所以我请求一些指导。

回答

3

您面临的主要问题是FlowDocument派生自DispatcherObject,因此您必须使用其Dispatcher来访问它。你试图用这个东西做的所有事情都会采取将项目放在Dispatcher的工作队列中并等待它执行它们的形式。其中,如果Dispatcher是处理用户界面的那个,则会导致您正在尝试避免的内容:Dispatcher正在执行您的工作项目时,剩下的所有鼠标单击和按键都堆积在Dispatcher'工作队列,用户界面将是不负责任的。

你从FlowDocument得到的内容是DispatcherObject,它的内容在长时间运行的任务处理时不能改变。在任务完成后,队列中的鼠标点击和击键可能会改变它,但在运行时,它们只会累积。这实际上很重要;如果您的能够使用Dispatcher避开,您将面临在任务运行时用户界面中的某些内容更改了FlowDocument的情况。那么你会有什么通常被称为“问题”。

即使您可以克隆FlowDocument并从UI的调度程序中断开该克隆,它仍然是一个DispatcherObject,并且您仍然遇到同样的问题,试图同时执行多个任务;你可以选择序列化你的访问权限或看你的后台线程崩溃。

要解决这个问题,你需要做的是制作一些非FlowDocument的非冻结快照。然后在快照上运行你的任务。这样,如果UI正在运行,并且在任务运行时更改FlowDocument,则不会搞乱您的游戏。

我该怎么办:使用XamlWriter并将FlowDocument序列化为XDocument。序列化任务涉及Dispatcher,但一旦完成,您可以根据需要运行尽可能多的古怪并行数据分析,UI中的任何内容都不会影响它。 (一旦它是一个XDocument你用XPath查询它,这是一个很好的锤子,只要你的问题实际上是钉子。)

+0

无法发表评论;抱歉 – SophieT 2011-05-23 16:08:24

+0

我试图使用Async = True访问FlowDocument,并且遇到了类似的问题。你从DispatchObject派生出来的解释清楚了事情。 +1 – Paparazzi 2011-09-19 00:56:21