在后台访问 WPF FlowDocument
我的问题涉及在 WPF 后台访问 UI 对象。我见过几十个示例应用程序,它们都很简单、易于理解,其中 95% 告诉你如何显示进度条。这并不是我想要的......
我的问题是这样的:我想通过访问 RichTextBox 中的 FlowDocument 来执行一项长任务(或许多长任务)。确切的任务与这里无关,但一个例子可能是扫描文档,并计算特定单词出现的次数,或者有多少个红色字符……。在一个很长的文档中,这些任务可能相当耗时,如果在前台完成,将会极大地占用 UI 并使其无响应。我只想解析 FlowDocument;我不想对其进行任何更改。
这就是我正在尝试做的事情。显而易见的解决方案是在后台执行此操作,但问题是……如何执行?我已经取得了什么成就appears作为一个答案,但它对我来说“感觉不对”,这就是我来这里寻求帮助的原因。
我的“解决方案”
我接下来的“解决方案”使用了一个BackgroundWorker,它调用UI对象的Dispatcher来确保访问正确的线程......并且它“看起来”可以完成这项工作。但真的吗?............
我已经大大简化了我的“解决方案”,以便(我希望)能够轻松地遵循我正在做的事情......
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 对象的 Dispatcher 处理的。所以BackgroundWorker实际上并不做任何工作。这就是我关心的部分;如果调度员忙于完成所有工作,我看不出我如何获得任何东西
Option 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,而后者会完成所有耗时的工作,并且一切都将在 UI 线程上进行。好吧……逻辑思维就这么多了……(对于 WPF 通常是致命的!)……事实并非如此。它会因常见的“不同线程”错误而崩溃。所以,再想一想,这个似乎是一个更优雅的解决方案,结果却是一个失败者!
我的问题如下:
- 我的“解决方案”到底是不是解决方案?
- 我哪里错了?
- 如何改进“解决方案”,使调度员不被长期任务束缚......这正是我试图避免的情况?
进一步的问题。请注意,我必须使用 FlowDocument 的调度程序才能完成这项工作。如果我使用 System.Windows.Threading.Dispatcher.CurrentDispatcher 代替,则不会调用 Delegate sub (DoTheTimeConsumingTask ),因此 – 出于所有意图和目的 – 什么也不会发生。有人可以解释一下为什么不可以吗?
我来找你并不是第一个停靠点。我已经尝试了几十个选项,但还没有找到任何感觉完全正确的选项(除了我的第二个选项不起作用,哈哈),所以我请求一些指导。