2015-11-06 67 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