3

ディレクトリの変更を監視するアプリケーションがあります。ファイル (ログ ファイル) が作成されると、その内容を分析し、結果をフォーム (既に存在し、初期化されていますが、現在は非表示になっている可能性があります) に書き込み、最後にこのフォームをユーザーに表示します。

アプリケーションが起動すると、タスクバーにアイコンだけが表示されます。メイン メソッドは、タスク バーにアイコンを作成し、結果を使用してフォームを監視/分析および制御するクラスを初期化するだけです。

public static void Main(string[] args) {
    NotificationIcon notificationIcon = new NotificationIcon();
    notificationIcon.notifyIcon.Visible = true;
    if (notificationIcon.Init()) {
        MainForm = ResultForm.GetInstance();
        Application.Run();
    }
}

「ResultForm」は先ほど触れたクラスで、問題に関連する次のメソッドがあります。

public static ResultForm GetInstance() {
    // _this is an attribute from the class. Is used to work always
    // with just one instance of the classe
    if (_this==null)
        _this= new ResultForm();

    return _this;
}

private ResultForm() {
    // initialization of the GUI form
    InitializeComponent();

    [...]

    // watcher for the log files
    logsWatcher= new FileSystemWatcher(LOGFILES_FOLDER);
    logsWatcher.Created += new FileSystemEventHandler(NewFile);
    logsWatcher.EnableRaisingEvents=true;
    logsWatcher.SynchronizingObject = this;
}

private void NewFile (object source, FileSystemEventArgs e) {
    // make sure the file is of the correct type
    [...]
    // perform some analysis on the file
    [...]
    // update the contents in the form (some TreeViews and labels)
    [...]

    // show the form to the user
    _this.Show();
}

そして今、問題が発生します。アプリケーションが起動し、ファイルが分析され、メイン フォームがまだ表示されていない場合、分析が完了すると、すべてが既に行われているにもかかわらず、「応答していません」と表示されます。後で新しいファイルが作成された場合、フォームはこの「応答なし」状態のままですが、正常に分析されます。

ただし、アプリケーションが開始されてからフォームが少なくとも 1 回開かれている場合 (たとえば、アイコンをダブルクリックし、フォームが表示され、フォームを閉じた場合 (または開いたままにしている場合でも問題ありません))、すべてがスムーズに動作します。

回避策として、次の 2 行 (Run()メソッドの前) でメインを変更できるため、ファイルが来る前にフォームが少なくとも 1 回表示されます。

MainForm.Show();
MainForm.Hide();

(分析が実行されるか、ユーザーが明示的にアイコンをクリックするまで表示されないようにするため、非表示にします。) それ以外は、プログラムに違いはありません。実行される作業は同じで、フォームは次のとおりです。すべてが完了すると、常に表示されます。実行中にメソッドの最後に到達することをデバッガーで確認しました。

上記の回避策を使用せずにこの問題を解決するにはどうすればよいですか?

このApplication.DoEvents()ようなコードのブロックまたはブロックを使用して、分析用のスレッドを作成しようとしました。最良の場合、フォームはすべてのコンテンツを適切に表示しますが、「応答なし」状態のままです。また、まったく同じ結果で呼び出しをメソッドに残そうとしましたが、これは負荷が高いという問題ではなく、間違っている可能性があることを示しています。Show()

EDIT @thecoon がリクエストしたので、問題を再現する小さなプロジェクトをアップロードしました。あなたもそれを使用する場合に備えて、それはSharpDevelopで行われました. --> http://dl.dropbox.com/u/1153417/test.zip

Main() メソッドに簡単な説明があります。

4

1 に答える 1

1

これは、ResultForm の開始方法と、FileSystemObjects同期オブジェクトの設定方法によって引き起こされるスレッドの問題であると強く思われます。

同期オブジェクトは、更新をマーシャリングするスレッドを効果的に選択します。この例では、GUI を表示しようとしているスレッドでもあるため、古き良きブロッキング操作に遭遇した可能性があります。または、多数のファイル システム イベントが原因でスレッドのコンテキスト スイッチが前後に切り替わっている可能性があります。急速。

スターターとして、代わりにこれを試してください:

public static void Main(string[] args) {
    NotificationIcon notificationIcon = new NotificationIcon();
    notificationIcon.notifyIcon.Visible = true;
    if (notificationIcon.Init()) {
        MainForm = new ResultForm();
        Application.Run(MainForm);
    }
}

ResultForm を直接 UI スレッドにマーシャリングすることに注意してください。

ResultForm を次のように変更します (これもシングルトンである必要はありません)。

public ResultForm() {
    // initialization of the GUI form
    InitializeComponent();

    [...]
    this.Load += ResultForm_Load;
}


protected void ResultForm_Load(object sender, EventArgs e)
{
    // watcher for the log files
    logsWatcher= new FileSystemWatcher(LOGFILES_FOLDER);
    logsWatcher.Created += new FileSystemEventHandler(NewFile);
    logsWatcher.EnableRaisingEvents=true;
    //Don't set the synchronization object - now all events from the FileSystemWatcher will be marshalled on a background thread
    Visible = false; //Hide the form if you want or minimize to tray or similar.
}

private void NewFile (object source, FileSystemEventArgs e) {

    if(InvokeRequired){
        //Ensures the file system events are marshalled back to the GUI thread
        Invoke(new MethodInvoker(() => {NewFile(source, e);}));
        return;
    }

    // make sure the file is of the correct type
    [...]
    // perform some analysis on the file
    [...]
    // update the contents in the form (some TreeViews and labels)
    [...]

    // show the form to the user
    Show(); //or Visible = true;
}

FileSystemWatcher の同期オブジェクトをフォームに設定しないことで、すべてのファイル システム イベントのマーシャリングが ThreadPool スレッドで確実に行われるようにします。New イベントが発生したら、InvokeRequired をチェックし、必要に応じてフォームの Invoke メソッドを呼び出して、UI スレッドにマーシャリングすることを忘れないでください。

アップデート

主な理由は、MainForm.Show を直接呼び出すか、Application.Run(MainForm) を介して呼び出すことにより、メッセージ ループが発生しているスレッドにフォームをプッシュすることです。

元のコードを使用してアプリを実行するNewFileと、 が呼び出されたときにApplication.MessageLoopfalse になります。

私の例のように、回避策、またはアプリケーションを表示する標準的な方法を使用する場合は、Application.MessageLoop真です。

ここでの私の最善の推測は、FileSystemWatcher が原因でフォームがデッドロックしていることです (これは、元の例でフォームを同期オブジェクトとして使用しているため、フォームを呼び出すことを意味しますBeginInvoke)。ただし、それは他のさまざまな問題である可能性もあります。Form.Show() は実際にはメソッドにかかっていますFPushMessageLoop- 無限ループに陥っています。

@HansPassant または @HenkHolterman は、これらの問題について広く投稿しています。たとえば、https: //stackoverflow.com/a/3833002/1073107 を参照してください。起動時にスプラッシュスクリーンなどを表示すると、すべての初期化が期待どおりに機能し、NewFile が正常に完了することに注意してください。つまり、プロセスを機能させたい場合は、アプリの起動時に何かを表示する必要があるようです。これは悪いことではないと思います。たとえば、ユーザーはアプリが開始され、現在トレイで実行されていることを確認でき、この問題に遭遇することはありません.

于 2012-06-25T15:27:47.930 に答える