简体   繁体   English

启动画面是否从未创建的线程访问?

[英]Splash screen being accessed from thread it was not created on?

I recently added a login form to my application. 我最近在我的应用程序中添加了一个登录表单。 This form shows prior to a splash screen that is shown while the main application form is loaded and various IO objects are instantiated. 该表单先于启动屏幕显示,该初始屏幕在加载主应用程序表单和实例化各种IO对象时显示。

Prior to the login form this is how my Program.cs would start the application 在登录表单之前,这是我的Program.cs将如何启动应用程序的方式

if (mutex.WaitOne(TimeSpan.Zero, true))
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    SplashScreen.ShowSplashScreen();
    Application.Run(MainForm.Instance);

    mutex.ReleaseMutex();
}

With the new login for the application is now started like so 现在,应用程序以新的登录名开始,如下所示

if (mutex.WaitOne(TimeSpan.Zero, true))
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    UserSessionSelection ussDialog = new UserSessionSelection();
    if (ussDialog.ShowDialog() == DialogResult.OK)
    {
        SplashScreen.ShowSplashScreen();
        Application.Run(MainForm.Instance);
    }

    mutex.ReleaseMutex();
}

Here is the SplashScreen class 这是SplashScreen

public partial class SplashScreen : Form
{
    public static SplashScreen Instance { get { return lazyInstance.Value; } }
    private static readonly Lazy<SplashScreen> lazyInstance = 
        new Lazy<SplashScreen>(() => new SplashScreen());

    private SplashScreen()
    {
        InitializeComponent();
        CenterToScreen();
        TopMost = true;
    }

    static public void NewLoadingUpdate(String message, int percent)
    {
        NewUpdateDelegate nud = new NewUpdateDelegate(NewLoadingUpdateInternal);
        SplashScreen.Instance.Invoke(nud, new object[] { message, percent });
    }

    static private void NewLoadingUpdateInternal(String message, int percent)
    {
        SplashScreen.Instance.lblLoadingText.Text = message;
        SplashScreen.Instance.pgProgress.Value = percent;
    }

    private delegate void NewUpdateDelegate(String message, int percent);
    private delegate void CloseDelegate();

    static public void ShowSplashScreen()
    {
        Thread thread = new Thread(new ThreadStart(SplashScreen.ShowForm));
        thread.IsBackground = true;
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    }

    static private void ShowForm()
    {
        Application.Run(SplashScreen.Instance);
    }

    static public void CloseForm()
    {
        SplashScreen.Instance.Invoke(new CloseDelegate(SplashScreen.CloseFormInternal));
    }

    static private void CloseFormInternal()
    {
        SplashScreen.Instance.Close();
    }
}

The error specifically happens with ShowForm the specific text is 该错误专门发生在ShowForm ,特定的文本是

An unhandled exception of type 'System.InvalidOperationException' 
occurred in System.Windows.Forms.dll

Additional information: Cross-thread operation not valid: Control
'SplashScreen' accessed from a thread other than the thread it was created on.

The error only happens about 1/20 times when the application starts. 当应用程序启动时,该错误仅发生约1/20次。 I never encountered it prior to the login form. 在登录表单之前,我从未遇到过它。

Any ideas as to what causes this? 关于什么原因的任何想法?

EDIT: For those late to the party, I think this SO question will help. 编辑:对于那些晚到党,我认为这个SO问题将有所帮助。 Wait for a thread to actually start in c# 等待线程实际在C#中启动

You need to create the SplashScreen on the same thread where you're using it. 您需要在使用SplashScreen的同一线程上创建它。

But wait, that's what I'm doing, isn't it? 但是等等,这就是我在做的,不是吗? Well, no - you're seeing a quite typical race condition. 好吧,不-您看到的是非常典型的比赛情况。

The core of your problem, I suspect, is using Lazy to initialize the splash screen, combined with not waiting for the form to be created in your ShowSplashScreen method. 我怀疑,问题的核心是使用Lazy初始化初始屏幕,同时不等待在ShowSplashScreen方法中创建表单。 In your main form, you refer to SplashScreen.Instance . 在您的主窗体中,您引用SplashScreen.Instance Now, if the first thread that tried to read the instance is your splashscreen message loop, you're fine - that's the 19 in 20. 现在,如果尝试读取该实例的第一个线程是您的启动屏幕消息循环,那么您就可以了-20之19。

However, it's perfectly possible that the main UI thread gets there first - you don't block in ShowSplashScreen . 但是,主UI线程很可能首先到达那里-您不会在ShowSplashScreenShowSplashScreen In that case, the splash screen is created on the main UI thread, and you're in trouble - and good thing you're not using InvokeRequired , because that would have hidden the error even further. 在这种情况下,启动屏幕是在主UI线程上创建的,您会遇到麻烦-而且您不使用InvokeRequired就是一件好事,因为那样会进一步隐藏错误。

Why does this have anything to do with the new login form? 为什么这与新的登录表单有关? Well, I suspect that it's a timing thing, really - your code is broken with or without the login form. 好吧,我怀疑这确实是一个计时问题,无论您是否使用登录表单,您的代码都已损坏。 However, ShowDialog starts a new message loop, similar to Application.Run . 但是, ShowDialog开始一个新的消息循环,类似于Application.Run This also means that a synchronization context has to be created - something that would otherwise only happen on your Application.Run(MainForm.Instance) line. 这也意味着必须创建一个同步上下文-否则只会在您的Application.Run(MainForm.Instance)行上发生。 The key point is that you've managed to make your race condition much wider - there is no longer as much time between the ShowSplashScreen call and the first time the splash screen is accessed in MainForm - and the result is BOOM. 关键点是您设法使比赛条件更加宽广ShowSplashScreen调用与MainForm首次访问初始屏幕之间的时间不再那么长-结果为BOOM。

Do not allow the ShowSplashScreen method to return until the instance is properly created, and you'll be fine. 在正确创建实例之前,不要让ShowSplashScreen方法返回,这样就可以了。 Multi-threading is hard - don't try to guess your way around. 多线程很难 -不要尝试猜测。 A good starting point would be http://www.albahari.com/threading/ - make sure you pay plenty of attention to proper synchronization and signalling. 一个很好的起点是http://www.albahari.com/threading/-确保您对适当的同步和信令给予了足够的关注。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 从不同的线程关闭启动画面? - Closing the splash screen from a different thread? 闪屏线程 - Splash screen thread 从不是在其上创建的线程的线程访问控件“ webBrowser1” - Control 'webBrowser1' accessed from a thread other than the thread it was created on 从不是在其上创建线程的线程访问控件“ dataGridView1” - Control 'dataGridView1' accessed from a thread other than the thread it was created on 从不是在其上创建线程的线程访问的控件“ x” - Control 'x' accessed from a thread other than the thread it was created on 从不是在C#中创建的线程的线程访问控件 - Control accessed from a thread other than the thread it was created on in C# 从不是在其上创建线程的线程访问控件“ ZedGraphControl” - Control 'ZedGraphControl' accessed from a thread other than the thread it was created on 跨线程操作无效:控制“ chart1”从其他线程访问,而不是在其上创建的线程 - Cross-thread operation not valid: Control 'chart1' accessed from a thread other than the thread it was created on 跨线程操作无效:从不是在其上创建线程的线程访问控件“ statusStrip” - Cross-thread operation not valid: Control 'statusStrip' accessed from a thread other than the thread it was created on 跨线程操作无效:从创建它的线程以外的线程访问控件 - Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM