#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..."; Progressp = 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.
Комментариев нет:
Отправить комментарий