如果之前没有显示,请填写“无响应”

时间:2012-06-25 15:02:30

标签: c# winforms

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

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

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

现在出现了问题。如果应用程序启动,则会分析文件并且尚未显示主窗体,分析完成后它将显示为“未响应”,尽管所有内容都已完成。如果之后创建了新文件,则会成功分析该文件,但表单将保持此“未响应”状态。

但是,如果表单在应用程序启动之前至少打开过一次(比如,双击图标,表单就显示出来并关闭它(或者将其打开,没关系))一切都会顺利进行。

作为一个workaroud,我可以使用以下两行(Run()方法之前)修改main,因此在任何文件出现之前,表单至少会显示一次:

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

(我将其隐藏起来,因为在执行分析或用户明确点击图标之前,它不应该是可见的。) 除此之外,程序没有区别:完成的工作是相同的,一旦完成所有工作,表格总是会显示出来。我已经确定调试器在执行期间到达了方法的结尾。

如果没有上述解决方法,我该如何解决这个问题?

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

修改 由于@thecoon要求它,我上传了一个重现问题的小项目。已经使用SharpDevelop完成,以防您也使用它。 - > http://dl.dropbox.com/u/1153417/test.zip

Main()方法中有一个小解释。

1 个答案:

答案 0 :(得分:1)

我强烈怀疑这是一个线程问题,一个是由你如何启动ResultForm引起的,另外一个是你如何设置FileSystemObjects synchronization object

同步对象有效地选择线程来编组更新。在这种情况下,它也是您尝试显示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成功完成;简而言之,如果您希望您的流程正常运行,您似乎拥有在应用启动时显示某些内容。我认为这不是一件坏事;例如,用户可以看到应用已启动并且现在正在托盘中运行,您可以确定不会遇到此问题。