Доступ к WPF FlowDocument в фоновом процессе - PullRequest
2 голосов
/ 21 мая 2011

Доступ к WPF FlowDocument в BackGround

Мой вопрос касается доступа к объекту пользовательского интерфейса в фоновом режиме в WPF.Я видел десятки примеров приложений, все они просты, за ними легко следить, и 95% из них рассказывают, как отображать индикатор выполнения.Это не совсем то, что я хочу… ..

Моя проблема заключается в следующем: я хочу выполнить длинную задачу (или много длинных задач), используя FlowDocument в RichTextBox.Точная задача здесь не имеет значения, но примером может быть сканирование документа и подсчет количества появлений конкретного слова или количества красных символов …….В длинном документе это может быть довольно трудоемким заданием, и, если оно будет выполнено на переднем плане, значительно сократит интерфейс и сделает его не отвечающим.Я только хочу разобрать FlowDocument;Я не хочу вносить в него какие-либо изменения.

Так вот, что я пытаюсь сделать.Очевидное решение - сделать это в фоновом режиме, но вопрос в том ... как?Я достиг того, что кажется ответом, но мне это просто «не кажется правильным», поэтому я здесь и прошу помощи.

Мой «Решение »

Мое следующее« решение »использует 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, почти ВСЕ долго выполняемая задача обрабатывается диспетчером объекта пользовательского интерфейса.Так что BackgroundWorker фактически не выполняет никакой работы.Это та часть, которая касается меня;Я не вижу, как я что-то получаю, если Диспетчер связан, выполняя всю работу

Вариант 2

Итак, мне показалось более логичным, чтоЯ бы лучше «немного перевернул это» и установил, что делегат Dispatcher указывает на подпрограмму, которая создает и запускает BackGroundWorker (я думаю, что поток Dispatcher будет тогда принадлежать потоку BackgroundWorker), и выполнять всю работув событии BackgroundWorker's 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, который, в свою очередь, будет выполнять всю долгую работу, и все будет в потоке пользовательского интерфейса.Ну ... так много для логического мышления ... (обычно фатально для WPF!) .... Это не так.Вылетает с обычной ошибкой «Другой поток».Итак, то, что казалось на первый взгляд гораздо более элегантным решением, оказалось проигравшим!

Мои вопросы следующие:

  1. Является ли мое «решение» aрешение или нет?
  2. Куда я иду не так?
  3. Как можно улучшить «решение», чтобы Диспетчер не связывался с долгой задачей…. Какова именно ситуацияЯ пытался избежать?

Еще один вопрос.Обратите внимание, что мне пришлось использовать диспетчер FlowDocument, чтобы сделать эту работу.Если бы вместо этого я использовал System.Windows.Threading.Dispatcher.CurrentDispatcher, то подпункт Делегат (DoTheTimeConsumingTask) не вызывался так - во всех смыслах и целях - ничего не происходит.Может кто-нибудь объяснить, почему нет, пожалуйста?

Я не пришел к вам в качестве первого порта захода.Я перепробовал десятки вариантов и пока не нашел ничего подходящего (кроме моего второго варианта, который не работает с LOL), поэтому прошу совета, пожалуйста.

1 Ответ

3 голосов
/ 21 мая 2011

Основная проблема, с которой вы сталкиваетесь, заключается в том, что FlowDocument происходит от DispatcherObject, и поэтому вам нужно задействовать его Dispatcher, чтобы получить к нему доступ.Все, что вы пытаетесь сделать с этой вещью, будет принимать форму помещения элементов в рабочую очередь Dispatcher и ожидания его выполнения для выполнения.Что, если Dispatcher - это тот, который обрабатывает пользовательский интерфейс, приведет именно к тому, чего вы пытаетесь избежать: пока Dispatcher выполняет ваш рабочий элемент, все оставшиеся щелчки мыши и нажатия клавиш накапливаютсяв рабочей очереди Dispatcher, и пользовательский интерфейс будет безответственным.

Что вы получаете из FlowDocument, будучи DispatcherObject, так это то, что его содержимое не может измениться, пока вы работаете долгозадача обрабатывает это.Эти щелчки мыши и нажатия клавиш в очереди могут изменить его, как только ваша задача будет выполнена, но пока она выполняется, они просто накапливаются.Это на самом деле отчасти важно;если бы вы смогли обойтись с помощью Dispatcher, вы бы столкнулись со сценарием, в котором что-то в пользовательском интерфейсе изменило FlowDocument во время выполнения вашей задачи.Тогда у вас появятся так называемые «проблемы».

Даже если бы вы могли клонировать FlowDocument и отключить клон от диспетчера пользовательского интерфейса, это все равно будет DispatcherObject, и вывсе еще сталкиваюсь с той же проблемой, пытаясь выполнить несколько задач одновременно;у вас есть возможность сериализовать ваш доступ к нему или посмотреть, как происходит сбой фонового потока.

Чтобы обойти это, вам нужно сделать некий DispatcherObject замороженный снимок FlowDocument.Затем запустите свою задачу на снимке.Таким образом, если пользовательский интерфейс изменяет FlowDocument во время выполнения вашей задачи, он не испортит вашу игру.

Что бы я сделал: использовать XamlWriter и сериализоватьFlowDocument в XDocument.Задача сериализации включает в себя Dispatcher, но как только она будет выполнена, вы можете запустить столько дурацких параллельных анализов данных, сколько захотите, и ничто в пользовательском интерфейсе не повлияет на это.(Кроме того, когда вы набираете XDocument, вы запрашиваете его с помощью XPath, что является довольно хорошим молотом, если ваши проблемы на самом деле являются гвоздями.)

...