[英]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线程很可能首先到达那里-您不会在
ShowSplashScreen
中ShowSplashScreen
。 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.