Программа зависает после выхода из заставки или блокировки компьютера - PullRequest
10 голосов
/ 15 ноября 2011

Наша программа работает нормально, пока кто-то не заблокирует компьютер или не появится заставка (но не ctrl + alt + delete) . Как только компьютер разблокирован / заставка закрыта, приложение перестает рисовать все, кроме строки заголовка, и перестает отвечать на ввод - оно отображает в основном белое окно, которое нельзя переместить или закрыть.

Example of application freezing

(пример зависания приложения - горы на моем рабочем столе)

Если мы оставим его на 5-10 минут, он вернется к жизни и не зависнет снова (даже после блокировки всплывающего окна компьютера / заставки) до перезапуска приложения.

Сложно отлаживать, потому что это не происходит при запуске программы из Visual Studio, а только при открытии .exe вручную.

Это происходит только при отображении заставки - если я удаляю код, чтобы отобразить заставку, это прекращается. Нам нужен экран-заставка, однако.

Я попробовал каждое предложение на этой странице ; единственное, с чем этого не происходит, это использование Microsoft.VisualBasic.WindowsFormsApplicationBase, но это вызывает всевозможных других проблем.

Информация об этом в Интернете недостаточна - кто-нибудь сталкивался с подобной проблемой раньше?


Вот соответствующий код:

//Multiple programs use this login form, all have the same issue
public partial class LoginForm<TMainForm>
    where TMainForm : Form, new()
{
    private readonly Action _showLoadingForm;

    public LoginForm(Action showLoadingForm)
    {
        ...
        _showLoadingForm = showLoadingForm;
    }

    private void btnLogin_Click(object sender, EventArgs e)
    {
        ...
        this.Hide();
        ShowLoadingForm(); //Problem goes away when commenting-out this line
        new TMainForm().ShowDialog();
        this.Close();
    }

    private void ShowLoadingForm()
    {
        Thread loadingFormThread = new Thread(o => _showLoadingForm());
        loadingFormThread.IsBackground = true;
        loadingFormThread.SetApartmentState(ApartmentState.STA);
        loadingFormThread.Start();
    }
}

Вот пример одного из _showLoadingForm действий, используемых в одной из программ:

public static bool _showSplash = true;
public static void ShowSplashScreen()
{
    //Ick, DoEvents!  But we were having problems with CloseSplashScreen being called
    //before ShowSplashScreen - this hack was found at
    ///28092/mnogopotochnyi-ekran-zastavka-v-c#28094
    using(SplashForm splashForm = new SplashForm())
    {
        splashForm.Show();
        while(_showSplash)
            Application.DoEvents();
        splashForm.Close();
    }
}

//Called in MainForm_Load()
public static void CloseSplashScreen()
{
    _showSplash = false;
}

Ответы [ 10 ]

3 голосов
/ 18 ноября 2011

Проблемы с заставкой

DoEvents очень нежелательна и не обязательно выполняет то, что вы думаете.DoEvents указывает CLR присутствовать в цикле сообщений Windows (для заставки), но не обязательно предоставляет время обработки другим потокам.Thread.Sleep() предоставит другим потокам возможность обрабатывать, но не обязательно позволит циклу сообщений Windows для вашего заставки продолжать отправку сообщений.Так что вам действительно нужны оба, если вы должны использовать цикл, но через минуту я порекомендую вообще отказаться от этого цикла.В дополнение к этой проблеме цикла, я не вижу какого-либо явного способа очистки потока-заставки.Вам нужно что-то вроде Thread.Join() или Thread.Abort().

Вместо использования цикла Application.DoEvents() я хотел бы использовать ManualResetEvent для синхронизации форм-заставок, запускаемых с вызывающим потоком.Таким образом, метод ShowSplash () не возвращается, пока не отобразится заставка.В любое время после этого мы, очевидно, можем закрыть его, так как знаем, что его показ завершен.

Вот цепочка с несколькими хорошими примерами: Многопоточные заставки .NET в C #

Вот как я изменил мой любимый пример, опубликованный @AdamNosfinger, для включения ManualResetEvent для синхронизации метода ShowSplash с потоком заставки:

public partial class FormSplash : Form
{
    private static Thread _splashThread;
    private static FormSplash _splashForm;
    // This is used to make sure you can't call SplashScreenClose before the SplashScreenOpen has finished showing the splash initially.
    static ManualResetEvent SplashScreenLoaded;

    public FormSplash()
    {
        InitializeComponent();

        // Signal out ManualResetEvent so we know the Splash form is good to go.
        SplashScreenLoaded.Set();
    }

    /// <summary>
    /// Show the Splash Screen (Loading...)
    /// </summary>
    public static void ShowSplash()
    {
        if (_splashThread == null)
        {
            // Setup our manual reset event to syncronize the splash screen thread and our main application thread.
            SplashScreenLoaded = new ManualResetEvent(false);

            // show the form in a new thread
            _splashThread = new Thread(new ThreadStart(DoShowSplash));
            _splashThread.IsBackground = true;
            _splashThread.Start();

            // Wait for the splash screen thread to let us know its ok for the app to keep going. 
            // This next line will not return until the SplashScreen is loaded.
            SplashScreenLoaded.WaitOne();
            SplashScreenLoaded.Close();
            SplashScreenLoaded = null;
        }
    }

    // called by the thread
    private static void DoShowSplash()
    {
        if (_splashForm == null)
            _splashForm = new FormSplash();

        // create a new message pump on this thread (started from ShowSplash)
        Application.Run(_splashForm);
    }

    /// <summary>
    /// Close the splash (Loading...) screen
    /// </summary>
    public static void CloseSplash()
    {
        // need to call on the thread that launched this splash
        if (_splashForm.InvokeRequired)
            _splashForm.Invoke(new MethodInvoker(CloseSplash));

        else
            Application.ExitThread();
    }
}

Основные проблемы с формой

Похоже, что вы запускаете свою основную форму из окна входа в систему, используя ShowDialog, а затем закрываете форму входа.Я правильно понял?Это не хорошо, если так.ShowDialog предназначен для дочерних окон вашего приложения и хочет иметь окно владельца, если вы не указываете форму владельца в аргументах метода, текущее активное окно считается владельцем.См. MSDN

Итак, ваша основная форма предполагает, что форма входа в систему является ее родителем, но вы закрываете форму входа вскоре после отображения основной формы.Поэтому я не уверен, в каком состоянии находится приложение на тот момент.Вместо этого следует рассмотреть возможность использования стандартного метода Form.Show() и простой настройки свойств формы, чтобы они отображались в виде диалога, если это желаемый результат (например, BorderStyle, MaximizeBox, MinimizeBox, ControlBox, TopMost).

ВАЖНОЕ РЕДАКТИРОВАНИЕ: Хорошо, я человек, я все испортил и забыл, что ShowDialog был методом блокировки.Хотя это устраняет проблему с ручкой владельца, я все же рекомендую не использовать ShowDialog для основной формы приложения, если только вы не можете предоставить существенное обоснование для него, которое не связано с внешним видом или потоками (поскольку это должно быть исправлено другими методами).Совет все еще здравый, несмотря на ошибку с моей стороны.

Возможные проблемы с окраской

Вы не указали, какие элементы управления вы использовали или выполняли какие-либо пользовательские рисунки в своем приложении.Но вы должны иметь в виду, что некоторые дескрипторы окон будут принудительно закрываться при блокировке компьютера.Например, если у вас есть какие-то пользовательские элементы управления для рисования, и вы кэшируете шрифты, кисти или другие ресурсы GDI, вам нужно иметь в своем коде несколько блоков try { ... } catch { ... }, которые утилизируют, а затем перестраивают кэшированные ресурсы GDI при возникновении исключения во время рисования.Я сталкивался с этим раньше, когда я рисовал окно списка и кэшировал некоторые объекты GDI.Если у вас есть какой-либо пользовательский код рисования где-либо в вашем приложении, в том числе на заставке, пожалуйста, проверьте, что все объекты GDI удачно расположены / очищены.

1 голос
/ 30 мая 2015

Спустя несколько лет (с кодом, уже не передо мной) , я добавлю ответ для всех, кто столкнулся с этой проблемой.


Проблема перевернуласьв точности как Ганс Пассант угадал .Проблема заключалась в том, что из-за некоторых невероятно неясных и безобидных ошибок в .Net Framework, InvokeRequired иногда может возвращать false, когда он должен возвращать true, в результате чего код, который должен выполняться в потоке графического интерфейса, запускается в фоновом режиме. (что из-за некоторых более неясных и безобидных ошибок вызывает поведение, которое я видел) .

Решение состоит в том, чтобы не полагаться на InvokeRequired, используя взлом, подобный этому:

void Main()
{
    Thread.Current.Name = "GuiThread";
    ...
}

bool IsGuiThread()
{
    return Thread.Current.Name == "GuiThread";
}

//Later, call IsGuiThread() to determine if GUI code is being run on GUI thread

Это решение, а также чрезвычайно углубленный взгляд на причины проблемы были найдены здесь .

1 голос
/ 18 ноября 2011

После добавления нескольких строк кода к фрагментам кода выше, я мог бы скомпилировать рабочую программу . Однако Я не смог воспроизвести проблему (Windows 7 Starter). Я попытался заблокировать компьютер и запустить заставку тоже. Я делал это, когда заставка была активна, и в других ситуациях, но главное окно всегда оставалось отзывчивым. Я думаю, что здесь должно происходить что-то еще, вероятно, во время инициализации главного окна.

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

using System;
using System.Threading;
using System.Windows.Forms; 

public class MainForm : Form
{
  //Here is an example of one of the _showLoadingForm actions used in one of the programs:
  public static bool _showSplash = true;
  public static void ShowSplashScreen()
  {
    //Ick, DoEvents!  But we were having problems with CloseSplashScreen being called
    //before ShowSplashScreen - this hack was found at
    ///28092/mnogopotochnyi-ekran-zastavka-v-c#28094
    using(SplashForm splashForm = new SplashForm())
    {
      splashForm.Show();
      while(_showSplash)
        Application.DoEvents();
      splashForm.Close();
    }
  }

  //Called in MainForm_Load()
  public static void CloseSplashScreen()
  {
    _showSplash = false;
  }

  public MainForm() 
  { 
    Text = "MainForm"; 
    Load += delegate(object sender, EventArgs e) 
    {
      Thread.Sleep(3000);
      CloseSplashScreen(); 
    };
  }
}

//Multiple programs use this login form, all have the same issue
public class LoginForm<TMainForm> : Form where TMainForm : Form, new()
{
  private readonly Action _showLoadingForm;

  public LoginForm(Action showLoadingForm)
  {
    Text = "LoginForm";
    Button btnLogin = new Button();
    btnLogin.Text = "Login";
    btnLogin.Click += btnLogin_Click;
    Controls.Add(btnLogin);
    //...
    _showLoadingForm = showLoadingForm;
  }

  private void btnLogin_Click(object sender, EventArgs e)
  {
    //...
    this.Hide();
    ShowLoadingForm(); //Problem goes away when commenting-out this line
    new TMainForm().ShowDialog();
    this.Close();
  }

  private void ShowLoadingForm()
  {
    Thread loadingFormThread = new Thread(o => _showLoadingForm());
    loadingFormThread.IsBackground = true;
    loadingFormThread.SetApartmentState(ApartmentState.STA);
    loadingFormThread.Start();
  }
}

public class SplashForm : Form
{
  public SplashForm() 
  { 
    Text = "SplashForm"; 
  }
}

public class Program
{
  public static void Main()
  {
    var loginForm = new LoginForm<MainForm>(MainForm.ShowSplashScreen);
    loginForm.Visible = true;
    Application.Run(loginForm);
  }
}
0 голосов
/ 24 ноября 2011

Я думаю, что ваша проблема в том, что вы используете Form.ShowDialog, а не Application.Run.ShowDialog запускает ограниченный цикл сообщений, который запускается поверх основного цикла сообщений и игнорирует некоторые сообщения Windows.

Примерно так должно работать:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault( false );

        Application.Run( new MainForm() );
    }
}


public partial class MainForm: Form
{
    FormSplash dlg = null;

    void ShowSplashScreen()
    {
        var t = new Thread( () =>
            {
                using ( dlg = new FormSplash() ) dlg.ShowDialog();
            }
        );

        t.SetApartmentState( ApartmentState.STA );
        t.IsBackground = true;
        t.Start();
    }

    void CloseSplashScreen()
    {
        dlg.Invoke( ( MethodInvoker ) ( () => dlg.Close() ) );
    }

    public MainForm()
    {
        ShowSplashScreen();

        InitializeComponent();

        Thread.Sleep( 3000 ); // simulate big form

        CloseSplashScreen();
    }
}
0 голосов
/ 24 ноября 2011

Вот выстрел в темноте: когда мы бездействуем, мы также просим нить идти спать.Я не уверен, что это поможет, но стоит попробовать:

    while(_showSplash) {
        System.Threading.Thread.Sleep(500);
        Application.DoEvents();
    }
0 голосов
/ 23 ноября 2011

Вы пытались использовать WaitHandle для отображения формы в ветке?

Что-то вроде:

EventWaitHandle _waitHandle = new AutoResetEvent(false);
public static void ShowSplashScreen()
{
    using(SplashForm splashForm = new SplashForm())
    {
        splashForm.Show();
        _waitHandle.WaitOne();
        splashForm.Close();
    }
}

//Called in MainForm_Load()
public static void CloseSplashScreen()
{
    _waitHandle.Set();
}
0 голосов
/ 23 ноября 2011

удалите эту строку, она вам не нужна, вы форсируете ее в один поток, когда по умолчанию используется mta.Возьмите значение по умолчанию.

loadingFormThread.SetApartmentState(ApartmentState.STA);

измените следующее:

using(SplashForm splashForm = new SplashForm())
{
    splashForm.Show();
    while(_showSplash)
        Application.DoEvents();
    splashForm.Close();
}

на:

SplashForm splashForm = new SplashForm())
splashForm.Show();

Измените это:

public static void CloseSplashScreen()
{
    _showSplash = false;
}

наэто:

public static void CloseSplashScreen()
{
    splashForm.Close();
}
0 голосов
/ 22 ноября 2011

В нашем приложении у нас были похожие проблемы с заставкой.Мы хотели иметь заставку с анимированным GIF-файлом (не вините меня, это было решение руководства).Это работает правильно только тогда, когда splashScreen имеет свой собственный цикл сообщений.Поскольку я думаю, что DoEvents является ключом к вашей проблеме, я покажу вам, как мы ее решили.Надеюсь, это поможет вам решить вашу проблему!

Мы покажем заставку следующим образом:

// AnimatedClockSplashScreen is a special form from us, it can be any other!
// Our form is set to be TopMost
splashScreen = new AnimatedClockSplashScreen(); 
Task.Factory.StartNew(() => Application.Run(splashScreen));

Заставка представляет собой простой файл, содержащий анимированный GIF-файлЧасы.У него нет петли, поэтому он не сгущается.

Когда всплеск нужно закрыть, мы делаем это следующим образом:

if (splashScreen != null)
{
    if (splashScreen.IsHandleCreated)
    {
        try
        {
            splashScreen.Invoke(new MethodInvoker(() => splashScreen.Close()));
        }
        catch (InvalidOperationException)
        {
        }
    }
    splashScreen.Dispose();
    splashScreen = null;
}
0 голосов
/ 18 ноября 2011

Из быстрого сканирования, которое я сделал для вашего кода, похоже, что ключом к вашей проблеме может быть использование

Application.Run(_splashForm);

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

0 голосов
/ 18 ноября 2011

так как нет рабочего примера

Вы можете попробовать удалить Application.DoEvents (); и вставить thread.sleep?

Application.DoEvents (); скажем, может быть очень злым.

...