Программа зависает после выхода из экранной заставки или запирающего компьютера

Наша программа работает нормально, пока кто-то не закроет компьютер или не закроется экранная заставка (но не 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
    ///info/93370/multi-threaded-splash-screen-in-c48946#48946
    using(SplashForm splashForm = new SplashForm())
    {
        splashForm.Show();
        while(_showSplash)
            Application.DoEvents();
        splashForm.Close();
    }
}

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

Ответ 1

Несколько лет спустя (когда код больше не передо мной), я добавлю ответ всем, кто испытывает эту проблему.


Вопрос оказался точно таким, как догадался Ханс Пассант. Проблема заключалась в том, что из-за некоторых невероятно неясных и безобидных ошибок в инфраструктуре .Net, 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

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

Ответ 2

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

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

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

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

Вот как я изменил свой любимый пример, который @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 красиво расположены/очищены.

Ответ 3

После добавления нескольких строк кода в фрагменты кода выше, я мог бы скомпилировать рабочую программу. Однако я не смог воспроизвести проблему (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
    //http://stackoverflow.com/questions/48916/multi-threaded-splash-screen-in-c/48946#48946
    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);
  }
}

Ответ 4

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

попробуйте удалить Application.DoEvents(); и вставить файл thread.sleep?

Application.DoEvents(); пусть может быть очень злой.

Ответ 5

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

Application.Run(_splashForm);

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

Ответ 6

В нашем приложении у нас были некоторые подобные проблемы с заставкой. Мы хотели иметь заставку с анимированным 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;
}

Ответ 7

удалите эту строку, вам это не нужно, вы вынуждаете ее в один поток, когда по умолчанию используется 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();
}

Ответ 8

Вы пытались использовать 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();
}

Ответ 9

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

    while(_showSplash) {
        System.Threading.Thread.Sleep(500);
        Application.DoEvents();
    }

Ответ 10

Я думаю, ваша проблема в том, что вы используете 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();
    }
}