为什么这段代码使我的表格消失了?

时间:2010-12-13 18:40:53

标签: c# .net winforms

由于我添加了一个启动画面,我的主窗体有时会(大约每20次一次)消失,就像它被最小化一样(它将是隐形但它仍然会在任务栏上,如果我点击它再次出现)。这是我的代码:

static class Program
{
    private static SplashScreen splashScreen = null;
    private static ManualResetEvent splashScreenWaiter = null;
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        ShowSplashAsync();
        BuilderForm2 builderForm2 = new BuilderForm2();
        builderForm2.Shown += new EventHandler(builderForm2_Shown);
        Application.Run(builderForm2);
    }

    private static void HideSplash()
    {
        if (splashScreenWaiter != null)
        {
            splashScreenWaiter.WaitOne();
            splashScreen.Invoke(new Action(splashScreen.Close));
            splashScreenWaiter = null;
            splashScreen = null;
        }
    }

    private static void builderForm2_Shown(object sender, EventArgs e)
    {
        HideSplash();
    }

    private static void ShowSplashAsync()
    {
        splashScreenWaiter = new ManualResetEvent(false);
        Thread splashThread = new Thread(ShowSplash);
        splashThread.IsBackground = true;
        splashThread.SetApartmentState(ApartmentState.STA);
        splashThread.Start(splashScreenWaiter);
    }

    private static void ShowSplash(object resetEvent)
    {
        splashScreen = new SplashScreen((ManualResetEvent)resetEvent);
        Application.Run(splashScreen);
    }
}

这是SplashScreen代码:

public partial class SplashScreen : Form
{
    private ManualResetEvent ResetEvent;
    bool handleCreated = false;
    bool formShown = false;

    public SplashScreen(ManualResetEvent resetEvent)
    {
        ResetEvent = resetEvent;
        HandleCreated += new EventHandler(SplashScreen_HandleCreated);
        InitializeComponent();
    }

    private void SetResetEventIfReady()
    {
        if(handleCreated && formShown) ResetEvent.Set();
    }

    private void SplashScreen_Shown(object sender, EventArgs e)
    {
       formShown = true;
       SetResetEventIfReady();
    }

    void SplashScreen_HandleCreated(object sender, EventArgs e)
    {
        handleCreated = true;
        SetResetEventIfReady();
    }
}

1 个答案:

答案 0 :(得分:4)

没有跳出来。但是代码中存在非常严重的竞争条件。它与SystemEvents类有关。该类为控件提供重要通知,以便他们可以响应用户更改Windows主题。该类需要一个隐藏的通知窗口来接收有关用户所做更改的消息。

如果您的程序的第一个窗口是在工作线程而不是UI线程上创建的,则非常错误。这使得SystemEvents类在错误的线程(而不是你的工作线程btw)上创建该通知窗口。它引发的事件将从该线程调用。在错误的线程上获取事件会造成严重破坏,控件不是线程安全的。最典型的结果是,当您锁定工作站时,您将遇到奇怪的绘画问题或形式死锁。我可以想象你看到的错误也可以用这个来解释。

.NET框架已经为启动画面提供了优秀且经过时间考验的支持。我建议你使用它而不是自己旋转。检查this answer代码。

如果你想保留自己的,那么你可以通过在ShowSplashAsync调用之前将这行代码粘贴到Main方法来解决竞争问题:

  Microsoft.Win32.SystemEvents.UserPreferenceChanged += delegate { };