3

我有一个应用程序可以监视目录的更改。创建文件(日志文件)时,它会分析其内容,将结果写入表单(该表单已存在并已初始化,尽管当前可能隐藏),最后将该表单显示给用户。

当应用程序启动时,任务栏中只显示一个图标。main 方法只是在任务栏中创建图标,并用结果初始化监视/分析和控制表单的类。

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();
}

现在问题来了。如果应用程序启动,分析了一个文件并且尚未显示主窗体,则在分析完成时将显示为“未响应”,尽管一切都已完成。如果之后创建了一个新文件,它将被成功分析,尽管表单将保持这种“无响应”状态。

但是,如果自应用程序启动后表单至少打开过一次(例如,您双击图标,显示表单并关闭它(或保持打开状态,没关系)),一切都会工作顺利。

作为一种变通方法,我可以使用以下两行(在Run()方法之前)修改 main ,因此在任何文件到来之前表单将至少显示一次:

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

(我将其隐藏起来,因为在执行分析或用户明确单击图标之前它不可见。)除此之外,程序没有区别:所做的工作是相同的,形式是一切完成后始终显示。我已使用调试器确保在执行期间到达方法的末尾。

如果没有提到的解决方法,我该如何解决这个问题?

我尝试创建一个用于分析的线程,使用Application.DoEvents()类似于的代码块。在最好的情况下,表单会正确显示其所有内容,但仍处于“未响应”状态。我还尝试让Show()方法中的调用得到完全相同的结果,这告诉我这不是负载过重的问题,而是我可能做错了什么。

编辑 由于@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.MessageLoop为 false。

如果您使用您的解决方法,或者按照我的示例显示应用程序的标准方式Application.MessageLoop是正确的。

我在这里最好的猜测是表单是死锁的,因为 FileSystemWatcher (因为它在原始示例中使用表单作为同步对象,这实际上意味着它调用BeginInvoke表单)。但是,它也可能是各种各样的其他问题;Form.Show() 实际上是挂在方法上的FPushMessageLoop——它陷入了无限循环。

@HansPassant 或 @HenkHolterman 都围绕这些问题进行了广泛的发布 -例如,请参阅https://stackoverflow.com/a/3833002/1073107 。请注意,如果您在启动时显示启动画面或类似内容,则所有初始化都按预期工作,并且 NewFile 成功完成;简而言之,如果您希望您的流程正常工作,您似乎必须在应用程序启动时显示一些内容我不认为这是一件坏事。例如,用户可以看到应用程序已经启动并正在托盘中运行,您可以确定不会遇到此问题。

于 2012-06-25T15:27:47.930 回答