【问题标题】:Console Application "pausing" c#控制台应用程序“暂停”c#
【发布时间】:2016-06-17 21:58:12
【问题描述】:

我有一个控制台应用程序,它使用 fileSystemWatcher 来监视新文件的映射网络路径。当文件出现时,它们会根据文件的内容进行重命名。我希望它 24/7 运行。它在一段时间内工作得很好,但我发现一段时间后控制台“暂停”并且似乎正在等待输入。如果我在控制台中点击返回,应用程序似乎赶上了所有似乎一直处于“待定”等待某种类型输入的事件。我似乎永远无法重现此问题以在暂停或原因的代码中捕获它。

我认为这与文件系统监视器由于外部因素(服务器重新启动、网络问题等)而失去监视网络路径的能力有关,因此我每 30 分钟重新创建一次文件系统监视器。我很确定这实际上与现在的情况无关。

基本上我所看到的是控制台正在愉快地打印出找到的新文件的更新等,但随后它会停止(程序操作,而不仅仅是输出)并且永远不会继续,直到我将焦点放在控制台应用程序上并按下某个键,然后所有未决事件(如计时器结束和文件系统观察程序重新创建)都会发生。

问题是:为什么会发生这种情况,我该如何预防?

程序的具体细节如下所示:

static void Main(string[] args)
{

    RefreshTimer = new Timer(1800000); //30 minutes
    RefreshTimer.Elapsed += RefreshTimer_Elapsed;
    RefreshTimer.Enabled = true;
    writeHelp();
    reCreateWatcher();

    getInput();

}


private static void writeHelp()
{
    Console.WriteLine("HELP:");

    Console.WriteLine(@"");
    Console.WriteLine("RENAMES FILES THAT ARE PLACED INTO THE WORKING FOLDER.");

    Console.WriteLine("*************_Commands:_************");
    Console.WriteLine("'runAll' = will run the program on all files curently existing in the folder.  Note that if a file has already been renamed the program SHOULD attempt the rename, find the file has already been renamed, and move to the next file.");
    Console.WriteLine("'quit' = exits program");
    Console.WriteLine("'help' = shows this spiffy information.");
    Console.WriteLine("[end]");
    Console.WriteLine("");


}

private static void getInput()
{
    Console.Write("Cmd ->");
    string k = Console.ReadLine();
    switch (k)
    {
        case "help":
            writeHelp();
            break;
        case "runAll":
            string[] allFiles = System.IO.Directory.GetFiles(PATH_TO_FILES);
            foreach (string curr in allFiles)
            {
                parseTheFile(curr);
            }
            break;
        case "quit":
            return;
        default:
            Console.WriteLine("Huh?  Unknown command!");
            break;
    }

    getInput();
}

private static void reCreateWatcher()
{

    Console.WriteLine("re-starting watcher...");
    Console.WriteLine("Starting Monitor of: " + PATH_TO_FILES);
    if (TheWatcher != null)
    {
        TheWatcher.Dispose();
    }
    deleteOldFiles();
    try
    {
        TheWatcher = new FileSystemWatcher(PATH_TO_FILES, "*.pdf");
    }
    catch (Exception ex)
    {

        Console.WriteLine("AN ERROR OCCURED WHEN TRYING TO SET UP THE FILE SYSTEM WATCHER ON, '" + PATH_TO_FILES + "'.  THE ERROR WAS: " + ex.Message);
        Console.WriteLine("Hit any key to exit.");
        Console.ReadKey();
        Environment.Exit(0);

    }

    TheWatcher.NotifyFilter = NotifyFilters.LastWrite;
    TheWatcher.Changed += theWatcher_Changed;
    TheWatcher.EnableRaisingEvents = true;
}
static void theWatcher_Changed(object sender, FileSystemEventArgs e)
{
    Console.WriteLine("New file found: " + e.Name);
    parseTheFile(e.FullPath);

}

【问题讨论】:

  • 您是否可以在控制台中使用鼠标选择文本?
  • 你有一个带有getInput()的无限递归程序。如果你不走运并且编译器决定不进行尾递归,你最终会得到一个 StackOverflow。只需使用 while(true) 循环即可。
  • 没有。这会在这种行为发生之前运行,有时会运行一天左右,而且任何人都无法做到这一点。
  • Scott- 好点,但如果有溢出能解释我所看到的吗?
  • 注意:在你做TheWatcher.Dispose();之前,先解绑事件TheWatcher.Changed -= theWatcher_Changed;,否则对象会因为事件而存活。

标签: c#


【解决方案1】:

要摆脱 Stackoverflow 异常(这可能是通过在 GetInput() 中调用 GetInput() 引起的)(就像 Scott 所说)使用 while 循环而不是递归方法调用。 p>

不需要重新创建 FileSystemWatcher。您在尝试读取文件时可能会遇到异常,因为它还没有完成写入。

一个使用while循环代替递归方法调用的例子:

class Program
{
    private static readonly string PATH_TO_FILES = @"d:\temp";

    static void Main(string[] args)
    {
        using (var watcher = new FileSystemWatcher(PATH_TO_FILES, "*.pdf"))
        {
            watcher.Changed += Watcher_Changed;

            WriteHelp();

            while (true)
            {
                Console.Write("Cmd ->");
                string k = Console.ReadLine();
                switch (k)
                {
                    case "help":
                        WriteHelp();
                        break;

                    case "runAll":
                        string[] allFiles = System.IO.Directory.GetFiles(PATH_TO_FILES);
                        foreach (string curr in allFiles)
                        {
                            parseTheFile(curr);
                        }
                        break;

                    case "quit":
                        return;

                    default:
                        Console.WriteLine("Huh?  Unknown command!");
                        break;
                }
            }


        }
    }

    private static void WriteHelp()
    {
        Console.WriteLine("HELP:");

        Console.WriteLine(@"");
        Console.WriteLine("RENAMES FILES THAT ARE PLACED INTO THE WORKING FOLDER.");

        Console.WriteLine("*************_Commands:_************");
        Console.WriteLine("'runAll' = will run the program on all files curently existing in the folder.  Note that if a file has already been renamed the program SHOULD attempt the rename, find the file has already been renamed, and move to the next file.");
        Console.WriteLine("'quit' = exits program");
        Console.WriteLine("'help' = shows this spiffy information.");
        Console.WriteLine("[end]");
        Console.WriteLine("");


    }

    private static void Watcher_Changed(object sender, FileSystemEventArgs e)
    {
        try
        {
            Console.WriteLine("New file found: " + e.Name);
            parseTheFile(e.FullPath);
        }
        catch(Exception exception)
        {
            Console.WriteLine("Exception: " + exception.Message);
        }
    }

    private static void parseTheFile(string fullPath)
    {
        throw new NotImplementedException();
    }
}

问题是,当 FileSystemWatcher 事件发生异常时,您永远不会重试。您可以将文件名添加到 List<string> 并创建一个尝试解析文件的计时器,如果成功,您可以将其从列表中删除。这可以通过命令runAll 来完成。无需直接解析文件,而是可以将所有文件添加到列表中。

【讨论】:

  • 对不起,但我认为这不是问题的答案,尽管@Scott 在他的建议中肯定是正确的 while(true)。由于 FileSystemWatcher 事件的触发方式,即使文件没有完成写入,我也将逻辑合并到 parseTheFile() 中,它会执行一些文件锁定检查和错误处理。但是,即使在事件中引发了错误,我认为它也不能解释我所看到的行为。
【解决方案2】:

啊!我想我知道发生了什么,很抱歉整个程序没有发布在这里,因为其他人肯定会马上看到。正如@Scott Chamberlain 所指出的,在 getInput() 方法中有递归。但是我也注意到,在 parseTheFile() 中还包括对 getInput() 的调用,具体取决于以下情况:

static private void parseTheFile(string thePath)
{
     Console.WriteLine("Attempting to parse " + System.IO.Path.GetFileName(thePath));
     string fileText;
     try
     {
        fileText = GetTextFromPdf(thePath);
     }
     catch (Exception ex)
     {
         Console.WriteLine("An error was encountered when trying to parse the file. " + ex.Message);
        getInput(); // <---- WHOOPS!
         return;
      }...

我显然有一些关于事件、静态方法和控制台应用程序的知识要学习。基本上我看到的情况是程序启动,转到 getInput(),它在字符串 k = Console.ReadLine(); 处等待。然而,该事件随后会针对一个新文件触发,然后该文件可能会在事件中再次调用 getInput()。在我点击返回之前,该事件无法完成,此外,正如@Jeroen 指出的那样,在重新创建 fileSystemWatcher 时我忽略了取消订阅该事件。这就解释了为什么当我点击返回时,所有其他事件都会产生这种级联效应?不知何故,控制台正在“暂停”等待这些事件中的所有这些“ReadLines”?我昨晚修复并运行了它,今天早上一切都很好,所以如果我再次看到问题,我会再次发布,但我很确定就是这样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-29
    • 2022-08-21
    • 2011-05-19
    • 2014-11-25
    • 1970-01-01
    • 2021-03-11
    • 1970-01-01
    相关资源
    最近更新 更多