none
Как определить причину зависания программы? RRS feed

  • Вопрос

  • Проблема в следующем. Есть приложение, написанное в WinForms и успешно работавшее несколько лет. Приложение включает в себя локальную авторизацию пользователя, по результатам которой запускаются разные формы для разных типов пользователей (упрощенно: есть единая стартовая форма, где указывается логин/пароль, затем запускается форма-1 или форма-2). Так вот при запуске формы-1 все прекрасно работает, при запуске формы-2 программа через неопределенный промежуток времени зависает (перестает реагировать на какие-либо действия пользователя - не нажимаются кнопки, недоступны поля и списки и пр.). При этом не выдается никаких ошибок! В режиме отладки воспроизвести зависание не получается. Посоветуйте, как можно выявить причину такого поведения программы?
    16 декабря 2014 г. 8:51

Ответы

Все ответы

  • Добрый день,

    ну попробуйте вести журналирование текущих действий программы и смотрите где она зависнет.

    Можно еще начать с Debugging a Deadlock

    16 декабря 2014 г. 9:06
  • Дополняя свой первый пост. В форме-2 может не выполняться вообще никаких действий. Простое открытие формы, и через некоторое время ее элементы управления перестают быть доступными. Пробовал запускать форму-2 сразу, минуя начальную форму с логином/паролем. Зависания нет (на протяжении длительного времени). Логично предположить, что дело не в форме-2 как таковой, а в вызове ее из начальной формы авторизации, но тогда почему те же действия не приводят к зависанию формы-1? Более того, вел лог по Timer'у в форме-2. Даже когда она становится зависшей, лог исправно пишется в базу данных.
    16 декабря 2014 г. 9:48
  • Таймер выполняется в отдельном потоке, следовательно может спокойно продолжать работать.
    16 декабря 2014 г. 9:56
  • Ок, а как тогда следует организовать "журналирование текущих действий программы"?
    16 декабря 2014 г. 9:59
  • В момент зависания подключите к процессу отладчик и сделайте паузу. По стеку вызовов определите, где "висит" программа.

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    16 декабря 2014 г. 12:25
  • К сожалению, не могу вставить картинку со стеком вызовов зависшей программы.

    Там перечислено следующее:

      

    [В спящем режиме, в режиме ожидания или на связи] mscorlib.dll!System.Threading.WaitHandle.WaitOne(long timeout, bool exitContext) + 0x23 байт System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle waitHandle) + 0x100 байт System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous) + 0x3f8 байт System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args) + 0x60 байт System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback d, object state) + 0x6f байт System.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.Invoke(bool checkFinalization = true, object[] args = {object[2]}) + 0xbb байт System.dll!Microsoft.Win32.SystemEvents.RaiseEvent(bool checkFinalization = true, object key = {object}, object[] args = {object[2]}) + 0x114 байт System.dll!Microsoft.Win32.SystemEvents.OnUserPreferenceChanging(int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x84 байт System.dll!Microsoft.Win32.SystemEvents.WindowProc(System.IntPtr hWnd = 15469300, int msg = 8218, System.IntPtr wParam = 0, System.IntPtr lParam = 127989472) + 0x4ec байт [Переход от машинного кода к управляемому] [Переход от управляемого кода к машинному] System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(int dwComponentID, int reason, int pvLoopData) + 0x5c3 байт System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason = -1, System.Windows.Forms.ApplicationContext context = {Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.WinFormsAppContext}) + 0x578 байт System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) + 0x65 байт Microsoft.VisualBasic.dll!Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.OnRun() + 0xf7 байт

    Microsoft.VisualBasic.dll!Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.DoApplicationModel() + 0x8c байт Microsoft.VisualBasic.dll!Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run(string[] commandLine) + 0x39d байт 


    Куда копать дальше?


    • Изменено Maks Schukin 17 декабря 2014 г. 5:51
    17 декабря 2014 г. 5:26
  • Анализируйте стек вызовов того потока, в котором выполняется Ваш код. Здесь я вижу только библиотечный код. Он висит на ожидании окончания обработки синхронного сообщения какому-то другому окну.

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    17 декабря 2014 г. 8:32
  • А как это сделать?

    Ниже информация по потокам. Больше мне ничего недоступно:

    9480	0	Поток рабочего процесса	<Без имени>		Максимальный
    9672	1	Основной поток	Основной поток	[В спящем режиме, в режиме ожидания или на связи]	Средний
    12756	0	Поток рабочего процесса	<Без имени>		Средний
    13100	6	Поток рабочего процесса	<Без имени>		Средний
    16660	0	Поток рабочего процесса	<Без имени>		Средний
    31800	7	Поток рабочего процесса	<Без имени>		Средний
    31992	8	Поток рабочего процесса	<Без имени>		Средний
    48068	4	Поток рабочего процесса	<Без имени>		Средний
    

    17 декабря 2014 г. 8:42
  • Двойной щелчок на потоке покажет Вам стек вызовов данного потока.

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    17 декабря 2014 г. 8:45
  • При двойном клике на первом потоке в списке (с приоритетом Максимальный) приводит к следующему:

    Нет доступных исходных файлов. Расположение стека вызовов: <пусто>

    При клике по Основному потоку получаю информацию из предыдущего моего сообщения. Плюс к этому:

    Нет доступных исходных файлов. Расположение стека вызовов: mscorlib.dll!System.Threading.WaitHandle.WaitOne(long timeout, bool exitContext) + 0x23 байт 

    17 декабря 2014 г. 8:51
  • Либо рядом с exe-модулем нет символов (pdb-файлов), либо они не соответствуют exe-модулю (старые). Вы приложение запускаете из под VS, т.е. непосредственно проект, или нет?

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    17 декабря 2014 г. 8:56
  • Я поступаю следующим образом. Поскольку ранее у меня не получилось смоделировать зависание из под VS в режиме отладки, то я запускаю .exe файл приложения вручную (не из под VS). Далее, как Вы мне вчера посоветовали, после зависания присоединяюсь к процессу зависшей программы (из под VS) и приостанавливаю отладку (Ctrl+Break). После этого получаю такую картину со стеками вызовов, которую я описал.
    17 декабря 2014 г. 9:54
  • Откройте проект в VS, перестройте все свои программные модули и запустите программу без отладки (ctrl+f5). Когда зависнет, подключайте отладчик.

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    17 декабря 2014 г. 10:04
  • Ок, сделал, как Вы сказали. Как только программа зависнет, отпишусь.
    17 декабря 2014 г. 11:29
  • Признаться - не очень понимаю, что мне дает новая информация из стека вызовов. Идет отсылка к

    блоку:

    Ogt.OnTimerRequest(Object source = {System.Timers.Timer}, System.Timers.ElapsedEventArgs e = {System.Timers.ElapsedEventArgs}) Строка 73 + 0x2f байт

    Точно такой же код используется в форме-1, которая не зависает. Ниже привожу чуть упрощенный кусок кода, который связан с данным участком программы. Может все же что-то не так?

    Public Class Ogt
    
      'Вызов формы уведомления
      Delegate Sub ShowNewRequestDelegate()
      Dim thNewRequest As ShowNewRequestDelegate
    
      'Открытие формы
      Private Sub Wrk_Menu_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
    
        'Создание интервала для проверки новых поступлений заявок
        AddHandler bTimer.Elapsed, AddressOf OnTimerRequest
        bTimer.Interval = 3000
        bTimer.Enabled = True
        bTimer.AutoReset = False
    
      End Sub
    
      'Проверка поступления новых заявок
      Sub OnTimerRequest(ByVal source As Object, ByVal e As System.Timers.ElapsedEventArgs)
    
        thNewRequest = New ShowNewRequestDelegate(AddressOf ShowNewRequest)
        Me.Invoke(thNewRequest)
    
      End Sub
    
      'Отображение формы уведомления
      Sub ShowNewRequest()
    ...
    
        'Вывод данных
        If copy + orig > 0 Then
          Dim frm_Check As New NewRequest
          frm_Check.Show()
          bTimer.Enabled = False
    
        'Обновлений нет
        Else
          bTimer.Interval = 10000
          bTimer.Enabled = True
          bTimer.AutoReset = True
        End If
      End Sub
    End Class

    18 декабря 2014 г. 10:22
  • Сделал другой вариант. Убрал из формы-2 весь сомнительный код. Фактически не осталось ничего, кроме одной кнопки, по которой можно отследить зависла программа или нет. Она зависла! Стек вызовов не показывает ничего (как в первой распечатке - Основной поток в режиме ожидания ссылается только на библиотечный код). Что же делать? Куда копать?
    18 декабря 2014 г. 13:43
  • А если вообще отказаться от таймера из System.Threading, а задействовать таймер из System.Windows.Forms? У Вас налицо взаимоблокировка потоков. Используя таймерные сообщения, Вы избавитесь от дополнительных потоков и, соответственно, от дополнительных проблем с синхронизацией.

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    18 декабря 2014 г. 14:48
  • Еще вариант. Вместо Invoke в обработчике таймера попробуйте BeginInvoke.

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    18 декабря 2014 г. 14:51
  • Переписал код с использованием System.Windows.Forms.Timer. Жду зависания...
    19 декабря 2014 г. 6:33
  • Снова зависание и снова без каких-либо внятных описаний. Основной поток в режиме ожидания. Есть еще пять (один с максимальным приоритетом) без данных. Откуда они берутся? Или это неверное направление для поисков решения?
    19 декабря 2014 г. 8:39
  • Переписал код с использованием System.Windows.Forms.Timer

    Надеюсь Вы понимаете, что теперь Invoke делать не нужно? Дополнительные потоки могут создаваться исполняющей средой .NET для служебных целей. Главное, чтобы Вы в своей программе не создавали "лишних" потоков. Тогда синхронизировать будет нечего.

    Выложите куда-нибудь проект, или просто "вырежете" из него проблемные куски и сделайте из них пример, я посмотрю сам.


    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    19 декабря 2014 г. 8:47
  • Надеюсь Вы понимаете, что теперь Invoke делать не нужно?

    Да, конечно. Invoke везде удален.

    Подскажите, как скомпоновать отдельно работающий проект, если в самом начале идет обращение к базе данных (для авторизации пользователя)? Да и в последующем тоже есть обращения к ней. Или Вам будет достаточно чистого кода?

    19 декабря 2014 г. 9:45
  • Что Вы называете "чистым" кодом? База данных меня, конечно, не интересует. Вы описали принцип работы Вашей программы: первая форма запускает вторую или третью в зависимости от некоторого условия, дальше происходит зависание. Вот этот кусок и было бы интересно посмотреть. Возможно это "вырезать"?

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    19 декабря 2014 г. 9:51
  • Под "чистым" кодом я имел ввиду - не работающее приложение, а набор кода в классах форм.

    Насчет вырезки этого куска - сейчас подумаю, как это реализовать без отсылки к базе данных. Только не пропадет ли при этом эффект зависания? 

    19 декабря 2014 г. 10:15
  •  Только не пропадет ли при этом эффект зависания? 

    Все может быть :) Ну, в таком случае, вот Вам и причина зависания. Если запрос к БД выполняется в потоке формы и он длительный по времени, форма "заснет".

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    19 декабря 2014 г. 10:30
  • Дело в том, что:

    а) абсолютно аналогичный код выполняется в форме-1 (которая не виснет).

    б) из формы-2 я удалил всю обработку, все обращения к базе данных и прочие моменты, которые потенциально могут приводить к зависаниям, а она все равно виснет!

    Это-то и ставит меня в тупик. Не понятно, на что грешить? 

    19 декабря 2014 г. 11:10
  • Кажется, я накопал участок программы, приводивший к зависанию. В стартовой форме используются два элемента BackgroundWorker. Видимо, не все корректно отрабатывало. Сейчас переписал код, проверяю зависание. По результатам - отпишусь.
    22 декабря 2014 г. 6:18
  • Все равно виснет! Ниже код стартовой формы. Как только добавляю два прогресс-бара для информирования пользователей о процессе авторизации, сразу получаю зависание. Где ошибка?

    Imports System.ComponentModel
    
    Public Class Passport
      Inherits System.Windows.Forms.Form
    
    #Region " Код, автоматически созданный конструктором форм Windows "
    ...
    #End Region
    
      'Переход к следующему элементу управления по Enter
      Private Sub NC_Enter(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles tb_00_Login.KeyDown, tb_00_Pswd.KeyDown
        If e.KeyValue = Keys.Enter Then SelectNextControl(sender, True, True, True, True)
      End Sub
    
      'Загрузка формы
      Private Sub Passport_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        bw.RunWorkerAsync()
      End Sub
    
      'Запуск процедуры
      Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles bw.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
        'Запуск асинхронной процедуры
        DataLoad(worker, e)
      End Sub
    
      'Определение имени последнего пользователя (автоматический запуск)
      Private Sub DataLoad(ByVal worker As BackgroundWorker, e As DoWorkEventArgs)
        Dim usr As New Sys_User
        Dim res As Integer
    
        Sys_User.ToggleConfigEncryption("app.exe")
        res = usr.CheckDomainName
    
        If res = 1 Then
          StartUp()
          e.Result = "Show FRM_APP"
        Else
          e.Result = usr.GetName
        End If
      End Sub
    
      'Окончание процедуры
      Private Sub bw_RunWorkerCompleted(ByVal worker As BackgroundWorker, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
        Select Case e.Result
          Case "Show FRM_APP"
            App_On()
    
          Case "SQL Error"
            Dim st As String
            st = "Дальнейшая работа невозможна" & System.Environment.NewLine & System.Environment.NewLine _
                      & "Программа будет закрыта"
            MsgBox(st, MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly, "Внимание")
    
          Case Else
            tb_00_Login.Text = e.Result
            Text_On()
        End Select
      End Sub
    
      'Авторизация пользователя
      Private Sub bt_00_User_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bt_00_User.Click
        tb_00_Null.Focus()
    
        tb_00_Login.Visible = False
        ll_00_Login.Visible = False
    
        tb_00_Pswd.Visible = False
        ll_00_Pswd.Visible = False
    
        bt_00_User.Visible = False
        pb.Visible = True
    
        bw2.RunWorkerAsync()
      End Sub
    
      'Запуск процедуры
      Private Sub bw2_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles bw2.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
    
        'Запуск асинхронной процедуры
        StartUp(tb_00_Login, tb_00_Pswd)
      End Sub
    
      'Окончание процедуры
      Private Sub bw2_RunWorkerCompleted(ByVal worker As BackgroundWorker, e As RunWorkerCompletedEventArgs) Handles bw2.RunWorkerCompleted
        App_On()
      End Sub
    
      'Запуск основной формы
      Private Sub App_On()
        If IsNothing(FRM_APP) <> True Then
          FRM_APP.Show()
          Me.Close()
        Else
          Text_On()
        End If
      End Sub
    
      'Отображение текстовых полей
      Private Sub Text_On()
        pb.Visible = False
        tb_00_Login.Visible = True
        ll_00_Login.Visible = True
    
        tb_00_Pswd.Visible = True
        ll_00_Pswd.Visible = True
    
        bt_00_User.Visible = True
    
        If tb_00_Login.Text <> "" Then
          tb_00_Pswd.Focus()
        Else
          tb_00_Login.Focus()
        End If
      End Sub

    22 декабря 2014 г. 6:37
  • Необходимо взглянуть на проект

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    22 декабря 2014 г. 7:01
  • Вырезал кусок кода из программы. Сформировал его в виде отдельного проекта. Жду когда зависнет.
    23 декабря 2014 г. 8:26
  • Не виснет...
    24 декабря 2014 г. 8:12