2015-11-06 72 views
0

我最近添加了一个登录表单到我的应用程序。该表单显示在主应用程序表单加载并实例化各种IO对象时显示的启动画面。启动屏幕正在从未创建的线程访问?

在此之前的登录表单这是我的Program.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倍。我在登录表单之前从未遇到过它。

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

编辑:对于那些晚会来说,我认为这个问题将有所帮助。 Wait for a thread to actually start in c#

回答

2

您需要在您使用它的同一个线程上创建SplashScreen

但是等等,这就是我正在做的,不是吗?呃,不 - 你看到一个非常典型的竞赛状况。

我怀疑你的问题的核心是使用Lazy初始化启动画面,并结合不等待在ShowSplashScreen方法中创建表单。在你的主表单中,你可以参考SplashScreen.Instance。现在,如果试图读取实例的第一个线程是你的飞溅屏幕消息循环,那么你很好 - 这就是20中的19.

然而,主UI线程首先到达那里是完全可能的 - t块在ShowSplashScreen。在这种情况下,主UI线程创建的启动画面,并且你就麻烦了 - 和你不使用InvokeRequired好事,因为这会进一步隐藏的错误。

为什么这与新的登录表单有关?那么,我怀疑这是一个计时问题,真的 - 你的代码被破坏,无论是否有登录表单。然而,ShowDialog启动一个新的消息循环,类似Application.Run。这也意味着必须创建一个同步上下文 - 否则只会发生在您的Application.Run(MainForm.Instance)行上。关键的一点是,你已经成功地使你的比赛状态更广泛 - 有不再是ShowSplashScreen呼叫并在第一时间启动画面在MainForm访问之间尽可能多的时间 - 其结果是BOOM。

不要让ShowSplashScreen方法返回,直到实例被正确创建,并且你会没事的。多线程是 - 不要试图猜测左右自己的方式。一个好的起点将是http://www.albahari.com/threading/ - 确保你对正确的同步和信号发送给予足够的关注。

+0

谢谢Luaan!我已经多次阅读/访问了albahari网站,虽然有很多信息需要介绍,而且多线程很难。 – KDecker