Страницы

Поиск по вопросам

среда, 11 декабря 2019 г.

TextRange.Load() в другом потоке

#c_sharp #wpf #многопоточность #flowdocument


Пишу WPF приложение. Есть кнопка, задача которой взять из MySQL таблицы массив байтов
(который на деле представляет из себя содержание TextRange) и загрузить его в FlowDocument.
Всегда это делал подобным образом:

FlowDocument flowDoc = new FlowDocument();
using (MemoryStream mem = new MemoryStream(bytesFromDB)
            {
                TextRange textRange = new TextRange(
                    flowDoc.ContentStart,
                    flowDoc.ContentEnd);
                textRange.Load(mem, DataFormats.XamlPackage);
            }


(В базу данных этот массив байтов записывается аналогичным образом. Записываю в MemoryStream
в формате XamlPackage из TextRange'a, выполняю метод ToArray() и отправляю в базу данных
этот массив. Но это всё не важно, перейду к самой проблеме.)

Всё прекрасно работало, пока я не решил код выше переместить в отдельный поток. Ибо
приложение замораживается, если массив байтов большой.

В случае отдельного потока, он просто выдает ошибку, что bytesFromDB содержит некорректную
информацию для формата XamlPackage, при выполнении:

textRange.Load(mem, DataFormats.XamlPackage);



  An exception of type 'System.ArgumentException' occurred in
  PresentationFramework.dll but was not handled in user code
  
  Additional information: Нераспознанная структура в формате данных
  "XamlPackage".


Т.е. в основном потоке он преобразует тот же самый массив байтов в XamlPackage нормально,
а в запуске отдельного потока (Пробовал и через Thread, Task и BackgroundWorker) он
выдает ошибку, что неверный формат.

Немного попробовав разные способы, понял, что он ругается только в том случае, если
TextRange содержит изображение (в FlowDocument), даже изображение в размере 1 пикселя
:D Хотя опять же, в основном потоке, textRange с изображенем хорошо загружается в MemoryStream.
    


Ответы

Ответ 1



Нашёл эту тему, наткнувшись на такую же проблему. Только у меня она возникла при разборе RTF-документа с картинкой в основном потоке консольного приложения. Решение нашёл быстро - оказывается, достаточно пометить Main атрибутом [STAThread]. В общем, логично - для работоспособности WPF и всего, что с ним связано, нужен STA-поток. Соответственно, прежде чем запускать поток, вызовете thread.TrySetApartmentState(ApartmentState.STA);

Ответ 2



(См. обновление в конце.) Прямая загрузка большого документа не очень хороша, т. к. блокирует UI-поток. Простой код var sw = Stopwatch.StartNew(); var doc = new FlowDocument(); var tr = new TextRange(doc.ContentStart, doc.ContentEnd); using (var stream = File.OpenRead(@"D:\CSharp Language Specification.rtf")) tr.Load(stream, DataFormats.Rtf); Viewer.Document = doc; sw.Stop(); Status.Text = "Finished in " + sw.Elapsed; грузил на моей машине спецификацию C# в качестве тестового документа около 10 секунд, всё это время UI был заморожен, что, разумеется, недопустимо. (Ещё 8 секунд занимала фоновая разбивка на страницы.) Имеет смысл грузить страницу в другом формате. Например, загрузка из формата XAML того же документа на моей машине происходит мгновенно. Но сама конвертация в этот формат получается намного дольше. Вот такой код: try { var sw = Stopwatch.StartNew(); Viewer.Document = null; Status.Text = "Loading..."; Progress p = new Progress(s => Status.Text = s); var xaml = await Task.Run( () => LoadAsXamlInBackground(@"D:\CSharp Language Specification.rtf", p)); Status.Text = "Rendering..."; var doc = (FlowDocument)XamlReader.Load(xaml); Viewer.Document = doc; sw.Stop(); Status.Text = "Finished in " + sw.Elapsed; } catch (Exception ex) { Status.Text = "Failed: " + ex.ToString(); } // ... MemoryStream LoadAsXamlInBackground(string filename, IProgress progress) { var ms = new MemoryStream(); var doc = new FlowDocument(); var tr = new TextRange(doc.ContentStart, doc.ContentEnd); using (var stream = File.OpenRead(filename)) tr.Load(stream, DataFormats.Rtf); progress.Report("Loaded, converting..."); XamlWriter.Save(doc, ms); progress.Report("Converted..."); ms.Position = 0; return ms; } не приводил к зависанию UI вовсе, но бежал существенно дольше. Код tr.Load(stream, DataFormats.Rtf) бежал всё те же 10 секунд, а вот XamlWriter.Save(doc, ms); заняло целых 80. Остальной под пробежал практически мгновенно. Перечитал вопрос, у вас уже сериализированный текст. Если так, всё проще. Вы можете Сохранить код в базу в таком виде, как он сохраняется в MemoryStream в методе LoadAsXamlInBackground. Этот метод медленный, но вам всё равно, т. к. это используется для создания, а не чтения. В читающем коде грузить MemoryStream из базы в фоновом потоке, и читать XamlReader'ом.

Ответ 3



> Пишу WPF приложение. > Всё прекрасно работало, пока я не решил код выше переместить в отдельный поток. если данные из отдельного потока надо выводить в контролы (созданы в UI-потоке), то надо использовать Dispatcher.

Ответ 4



Прежде чем запускать поток, нужно вызвать TrySetApartmentState (ApartmentState.STA). Пример кода: Thread th = new Thread (delegate () { load (); }); th.TrySetApartmentState (ApartmentState.STA); th.Start (); Где load() функция в которой идет загрузка в TextRange. Без th.TrySetApartmentState (ApartmentState.STA) вылетает Exception.

Комментариев нет:

Отправить комментарий