初始屏幕正在从未在其上创建的线程访问

本文关键字:创建 访问 线程 屏幕 | 更新日期: 2023-09-27 18:20:48

我最近在我的应用程序中添加了一个登录表单。此窗体在加载主应用程序窗体并实例化各种 IO 对象时显示的初始屏幕之前显示。

在登录表单之前,这就是我的程序.cs启动应用程序的方式

if (mutex.WaitOne(TimeSpan.Zero, true))
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    SplashScreen.ShowSplashScreen();
    Application.Run(MainForm.Instance);
    mutex.ReleaseMutex();
}

使用应用程序的新登录现在像这样启动

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

这是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();
    }
}

错误具体发生在特定文本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.

当应用程序启动时,该错误仅发生大约 1/20 次。在登录表单之前,我从未遇到过它。

关于是什么原因造成的任何想法?

编辑:对于那些迟到的人,我认为这个问题会有所帮助。等待线程在 c# 中实际启动

初始屏幕正在从未在其上创建的线程访问

您需要在

使用它的同一线程上创建SplashScreen

但是等等,这就是我正在做的,不是吗?好吧,不 - 你看到的是一个非常典型的竞争条件。

我怀疑,您的问题的核心是使用Lazy来初始化初始屏幕,而不是等待在ShowSplashScreen方法中创建表单。在您的主窗体中,您指的是 SplashScreen.Instance .现在,如果尝试读取实例的第一个线程是您的启动画面消息循环,那很好 - 这是 19 中的 20。

但是,主 UI 线程完全有可能首先到达那里 - 您不会阻止ShowSplashScreen .在这种情况下,初始屏幕是在主 UI 线程上创建的,您遇到了麻烦 - 还好您没有使用 InvokeRequired ,因为这会进一步隐藏错误。

为什么这与新的登录表单有什么关系?好吧,我怀疑这是一个时间问题,真的 - 无论有没有登录表单,您的代码都会损坏。但是,ShowDialog启动一个新的消息循环,类似于 Application.Run 。这也意味着必须创建一个同步上下文 - 否则只会发生在您的Application.Run(MainForm.Instance)行上。关键点是,您已经设法使竞争条件更加宽广 - ShowSplashScreen调用和首次访问初始屏幕之间不再有那么多时间 MainForm - 结果是 BOOM。

在正确创建实例之前,不要允许 ShowSplashScreen 方法返回,这样您就会没事的。多线程很难 - 不要试图猜测你的方式。一个好的起点是 http://www.albahari.com/threading/- 确保你充分注意正确的同步和信令。