初始屏幕正在从未在其上创建的线程访问
本文关键字:创建 访问 线程 屏幕 | 更新日期: 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/- 确保你充分注意正确的同步和信令。