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

наша программа работает нормально, пока кто-то не блокирует компьютер или экранная заставка всплывает (но не 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
    //https://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;
}

10 ответов


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

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

вместо Application.DoEvents() loop, мне нравится использовать 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();
    }
}

Основные Вопросы Формы

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

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

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

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

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


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

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

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);
  }
}

через несколько лет (С кодом больше не передо мной), Я добавлю ответ для всех, кто испытывает эту проблему.


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

решение не полагаться на 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

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


Так как нет примера работающего

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

приложение.DoEvents (); Пусть говорят, может быть очень злой.


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

Application.Run(_splashForm);

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


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

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

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

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

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

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