3

かなり長い間実行されている C#/TCP/Winform アプリがありますが、ユーザーの 1 人が、簡単に再現できる問題を発見しましたが、解決できないようです。

アプリが Windows 7 で開いているときに、ユーザーがスクリーンセーバー設定のオプションを変更すると (使用しているスクリーンセーバーや時間など)、アプリがフリーズし、UI が応答しなくなります。フォームの任意の場所をクリックすると、システムが "音" を鳴らすだけです。

アプリがデバッグ モードで Visual Studio を介して実行されると、問題はなくなりますが、VS の制限を超えると、フリーズ状態に戻ります。

いくつかの試行錯誤とテストの後、問題を 1 秒ごとに実行される System.Threading.Timer に絞り込んだようです。私はタイマーが何をするのかを着実に掘り下げてきましたが、タイマーのイベントが何もしない場合でも、アプリをロックしていることを発見しました。コードでタイマーを無効にするか、スクリーンセーバーの設定を変更する前にアプリでタイマーをキャンセルすると、スクリーンセーバーの変更後にアプリが機能するようになります (ただし、約 2 ~ 3 秒間フリーズしているように見えます)。

このコードは、アプリをフリーズ可能にするために必要なすべてのようです (WinForm コード内):

    /// <summary>
    /// Starts the countdown timer for unit alerts
    /// </summary>
    private void startCountDownTimer()
    {
        Object timeState = new object();
        this._timerCall = new System.Threading.TimerCallback(this.countDown);
        this._countdownTimer = new System.Threading.Timer(_timerCall, timeState, 0, 1000);
    }

    /// <summary>
    /// Invokes the countdown logic for unit alerts
    /// </summary>
    /// <param name="state"></param>
    private void countDown(Object state)
    {
        // REMOVED AND STILL FREEZING
    }

このフリーズは、countDown の内容がコメントアウトされていても発生するため、タイマーは 1 秒に 1 回起動するだけであることに注意してください。

プロセスを起動してリモート デバッガーをアタッチしても、アプリケーションに問題があることを示す出力は何もありません。フォームは実際には「Activated」のようなイベントを発生させます:

*** MAIN WINDOW ACTIVATED ***
*** MAIN WINDOW ACTIVATED ***

ただし、他に何も起動していないようで、EndTask を介してアプリケーションを強制終了またはシャットダウンする必要があります。デバッガーがまだアタッチされているときに EndTask を使用すると、突然エラーが発生します。

A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
===================================
ERR: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
       at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
   at System.Windows.Forms.Control.BeginInvoke(Delegate method, Object[] args)
   at System.Windows.Forms.Control.BeginInvoke(Delegate method)
   at Wcsg.UI.Windows.CadClient.client_MessageReceived(TcpMessageReceivedEventArgs mrea) in C:\Users\---------\Documents\Visual Studio 2010\Projects\Dispatch-DEVELOPMENT\CadClient\Forms\CadClientForm.cs
   at Wcsg.Net.Tcp.WcsgTcpClient.processStream(Int32 count)
   at Wcsg.Net.Tcp.WcsgTcpClient.performSocketRead(IAsyncResult ar)
---------------------

私が最終的に得たエラーは、最終的にソケット上のメッセージの処理に取り掛かる間、フォームの終了に関連しているようです。

私はここを見るためのあらゆる種類の方向性を探しています。

* 編集 *

予想される回避策 (回答を参照) が機能しなかったときに、プログラムの Main メソッドについて尋ねられました。メインコードは次のとおりです。

[STAThread]
    static void Main(String[] args)
    {
        // check for other running CadClients
        bool createdNew = _mutex.WaitOne(TimeSpan.Zero, false);

        if (createdNew) // first-run, launch Status Monitor on load
        {
            List<String> newArgs = new List<string>(args);
            newArgs.Add("-SM");
            args = newArgs.ToArray();
        }

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        AppDomain currentDomain = AppDomain.CurrentDomain;
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

        // try to catch issue 13445 -- see testing notes
        CadClient cc = new CadClient(args);
        CadExceptionHandler ceh = new CadExceptionHandler(ref cc);
        currentDomain.UnhandledException += new UnhandledExceptionEventHandler(ceh.CurrentDomain_UnhandledException);
        Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(ceh.Application_ThreadException);

        Application.Run(cc);
    }

*編集* おそらくスプラッシュスクリーンコードも追加する必要があります:

    private void showSplashScreen()
    {
        WaitCallback wcb = new WaitCallback(doSplashScreen);
        ThreadPool.QueueUserWorkItem(wcb);
    }

    private void doSplashScreen(object state)
    {
        if (this._splash == null)
            this._splash = new SplashForm(this);

        this._splash.FormClosed += new FormClosedEventHandler(_splash_FormClosed);
        this._splash.Show();

        while (this._splash != null && !this._splash.WorkDone)
            Application.DoEvents();
        this._splash.Close();
    }

メソッド showSplashScreen() は、メイン フォームのコンストラクター内で呼び出されます。もともとは InitializeComponents() 呼び出しの前と後で呼び出されます。スプラッシュ スクリーンの表示は、メイン アプリが何らかのセキュリティを検証している間に表示を更新します。次に、ログイン画面に変わり、メインアプリがサーバーからデータをロードしている間、さらに更新が表示されます。メイン フォームが (イベントを介して) 作業を完了したことを示すと、SplashScreen は閉じられます。

4

1 に答える 1

3

@HansPassant からのコメントを使用したところ、スプラッシュ/ログイン画面の showSplash メソッドが Application.Run の前に呼び出されていたのではなく、メイン フォームの InitializeComponent メソッドの前に呼び出されていたことがわかりました。this.showSplash を InitializeComponent() のすぐ下の行に移動して再コンパイルしたところ、問題はなくなったようです。

于 2012-04-17T21:23:05.660 に答える