Страницы

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

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

Диагностика GUI RichTextBox

#c_sharp #winforms #многопоточность #gui


Приложение наглухо зависает при входе через RDesktop (стандартный удаленный рабочий
стол Windows) на удаленный Windows Server 2008 (вышел, зашел - ого, зависло). Проблема
сводится к такому простейшему коду:

  ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
  {
     var richTextBox = new RichTextBox();

     while (true)
     {
       richTextBox.AppendText("a");//источник зла
     }

   }), SynchronizationContext.Current);


richTextBox изначально в дочернем потоке, поэтому InvokeRequired=false, конфликта
с GUI не должно быть.
Вопрос-в чем может быть проблема, с чего начать диагностику?

Update

еще такой взгляд на проблему:

  ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
  {
       var richTextBox = new RichTextBox();

       //Thread.Sleep(10000);//вышел-зашел в RDesktop -все нормально
       richTextBox.AppendText("a");//источник зла
       Thread.Sleep(10000);//вышел-зашел в RDesktop- приложение виснет

   }), SynchronizationContext.Current);


Update 2

Такая же ошибка возникает при экранной заставке (splash screen).

Еще по теме:


Debugging Windows Forms Application Hangs During SystemEvents.UserPreferenceChanged
Mysterious Hang or The Great Deception of InvokeRequired 
Memory Leak in ToolStripTextBoxControl
What is UserPreferenceChangedEventHandler in C# winform applications?
.NET 4.0 and the dreaded OnUserPreferenceChanged Hang


Изначальная задача

Требуется в отдельном (не основном) потоке сформировать большой RTF. RTF необходим,
так как требуется форматирование шрифта (разные размеры шрифта и проч.) Затем сохранить
в файл richTextBox.Save(файл.rtf)
    


Ответы

Ответ 1



Лучше сделать поток не в очереди, а напрямую Thread.Start. Добавьте обязательно Application.DoEvent в теле потока. Поток сделать STA.

Ответ 2



У меня эта (или похожая) проблема воспроизводится на Win 2008 R2, но подвисает не полностью, а секунд на 10. WinDbg показывает следующий трейс: 0043e8f8 779801a9 [HelperMethodFrame_1OBJ: 0043e8f8] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean) 0043e9c4 70726dd2 System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean) 0043e9e0 70726d9c System.Threading.WaitHandle.WaitOne(Int32, Boolean) 0043e9f4 6df04246 System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle) 0043ea30 6e2657bf System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean) 0043ea34 6defce3b [InlinedCallFrame: 0043ea34] 0043eabc 6defce3b System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[]) 0043eaf0 6e13bf99 System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback, System.Object) 0043eb08 700c5c23 Microsoft.Win32.SystemEvents+SystemEventInvokeInfo.Invoke(Boolean, System.Object[]) 0043eb3c 700c5533 Microsoft.Win32.SystemEvents.RaiseEvent(Boolean, System.Object, System.Object[]) 0043eb90 700c5123 Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(Int32, IntPtr, IntPtr) 0043ebb0 7028bb60 Microsoft.Win32.SystemEvents.WindowProc(IntPtr, Int32, IntPtr, IntPtr) 0043ebb4 000ca24a [InlinedCallFrame: 0043ebb4] 0043ed64 000ca24a [InlinedCallFrame: 0043ed64] 0043ed60 6d9c976c DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef) 0043ed64 6d97f341 [InlinedCallFrame: 0043ed64] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef) 0043ed98 6d97f341 System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32) 0043ed9c 6d97efc1 [InlinedCallFrame: 0043ed9c] 0043ee24 6d97efc1 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext) 0043ee74 6d97ee32 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext) 0043eea0 6d958a41 System.Windows.Forms.Application.Run(System.Windows.Forms.Form) 0043eeb4 001e007a WindowsFormsApplication2.Program.Main() 0043f03c 71463de2 [GCFrame: 0043f03c] После этого в отладке выпадает пачка исключений System.ComponentModel.InvalidAsynchronousStateException An error occurred invoking the method. The destination thread no longer exists. Судя по всему, при входе через ремоут система пытается прислать всем окнам OnUserPreferenceChanged. RTB - окно, и до него сообщение тупо не доходит - потому что поток, в котором RTB создан, в это время занят добавлением букв или сном. У меня перестает подвисать, если брать поток не из пула, а создавать новый: new Thread(() => { var richTextBox = new RichTextBox(); //Thread.Sleep(10000);//вышел-зашел в RDesktop -все нормально richTextBox.AppendText("a");//источник зла Thread.Sleep(100);//вышел-зашел в RDesktop- приложение виснет }).Start(); Исключения все еще видны в отладке, но при этом они выбрасываются сразу же, и зависания нет. UP: по UserPreferenceChanged находится описание конкретно этой проблемы: Debugging Windows Forms Application Hangs During SystemEvents.UserPreferenceChanged - с подробной инструкцией по отладке подобных проблем. KB943139 :Windows Forms application freezes when system settings are changed or the workstation is locked - KB с описанием именно этой проблемы. Applications should never leave Control objects on threads without an active message pump. If Controls cannot be created on the main UI thread, they should be created on a dedicated secondary UI thread and Disposed as soon as they are no longer needed. Так что если хотите надежного решения - запускайте вторую помпу: new Thread(() => { Application.Run(new Скрытая_Форма_С_RTB_на_ней()); }).Start(); и закрывайте эту форму сразу как она перестанет быть нужна.

Ответ 3



Проблема возникает от неполного понимания основ многопоточного программирования. В дочерних потоках не следует использовать компоненты UI, имеющие окно. Точка. RichTextBox порожден от System.Windows.UIElement, и, стало быть, имеет дескриптор окна. Большинство подобных контроллов не являются thread-safe и выполнение их кода следует дополнительно программировать. На выполнении метода richTextBox.AppendText("a") из дочернего потока происходит неавторизованное вмешательство в исполнение кода главного потока. Результатом, как правило, является исключение. Если уж вы их используете, то передачу результатов из дочернего потока (выполняющего логику) в главный следует запрограммировать при помощи обратных вызовов (Callbacks). Несколько отличных статей по теме (англ. яз): RichTextBox Class How to: Make Thread-Safe Calls to Windows Forms Controls Threads and Conrtols полный ответ на вопрос Using RichTextBox in a thread?

Ответ 4



Только что увидел вот это: Изначальная задача -требуется в отдельном (не основном) потоке сформировать большой RTF. RTF необходим, так как требуется форматирование шрифта (разные размеры шрифта и проч.) Немного смущают некорректные формулировки, из-за которых и возникают досадные ошибки и непонимания. Будьте последовательны и точны в описание задач. В частности, RTF - это формат данных, а то что вам нужно, называется, судя по всему Rich Text. Если я правильно понимаю задачу, то происходит следующее: Запускается приложение (главный поток) в виде Windows-формы. Форма - это визуальный компонент. Она служит контейнером (родителем) для всех других визуальных компонентов в приложении. Затем запускается дочерний поток, в котором автор хочет разместить визуальный компонент RichTextBox (правильно?) Компонент имеет окно. И для того, чтобы ему где-то "разложить свои вещички", он должен иметь родителя (parent) - тот самый контейнер, которым является форма или панель. Если нет родителя, то компонент попросту не сможет инициализироваться. Помимо этого, и компонент, и дочерний поток должны делать маршаллинг данных для взаимодействия как друг с другом, так и с основным потоком. Как это будет выглядеть в приведенной выше схеме, лично я не представляю. Вернее, не будет это никак выглядеть. Потоковым программистам следует понимать, что почти* весь визуальный интерактив должен реализовываться в основной форме приложения и/или в диалоговых окнах. В основном интерфейсе пользователя. А потоки - это рабочие лошадки, задача которых - выполнение тяжелой и времяёмкой работы (запустить выборку из базы, сказачть заголовки с IMAP-сервера, прорисовать, считать и распарсить файл и пр.). Безусловным достоинством потока является так же возмождость "разморозить" (unfreeze) экран основной формы на время исполнения тяжёлых задач. Однако любые попытки разместить в потоках не thread-safe-контроллы будут приводить к исключениям. Вывод: Задача сформулирована неправильно; приведенными в задаче методиками ее решить нельзя. Поэтому я предложил бы автору вопроса, перепланировать подход к решению. Прежде всего задать себе вопрос - а есть ли в данном случае реальная необходимость использовать потоки? *В Windows существуют техники, когда потоки непосредственно взаимодейстуют с элементами формы через message loops. Такие поток иимеют условное отношение к GUI. Например, они могут прорисовывать канву, или делать repaint по какому-нибудь событию. Эти потоки работают на низком уровне ОС и в рамках данного поста мы о них речь не ведём.

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

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