运行 windows 服务中的异步外部进程

Running external processes asynchronously in a windows service

我正在编写一个程序,将 csv 文件从 "queue" 文件夹移动到 "processing" 文件夹,然后启动名为 import.exe 的第三方进程,将 csv 文件路径作为一个论点。 Import.exe 是一个很长的 运行 任务。

我需要程序继续 运行 并检查队列中的新文件。出于这个原因,我选择了 Windows 服务应用程序,因为它会很长 运行。

我的问题是我被选项淹没了,无法理解我是否应该用 background threads or parallel programming 或很可能是两者的组合来解决这个问题。

到目前为止,我有这段代码只是 运行 同步。 您很快就会发现,此时此刻,我只是疯狂地启动进程,而没有管理或检查是否完成。我已经注释掉 process.WaitForExit (),因为显然这是一个阻塞调用。

public int maxConcurrentProcesses = 10;
protected override void OnStart(string[] args)
    {
        // Set up a timer to trigger every minute.
        System.Timers.Timer timer = new System.Timers.Timer(60000);            
        timer.Elapsed += new System.Timers.ElapsedEventHandler(this.OnTimer);
        timer.Start();
    }

private void OnTimer(object sender, System.Timers.ElapsedEventArgs args)
    {            
        // How many instances of import.exe are running?
        Process[] importProcesses = Process.GetProcessesByName("import");
        int countRunning = importProcesses.Count();

        // If there are less than maxConcurrentProcesses, create as many as needed to reach maxConcurrentProcesses
        if (countRunning < maxConcurrentProcesses)
        {
            int processesToStart = maxConcurrentProcesses - countRunning;
            for (int i = 0; i < processesToStart; i++)
            {
                FireOffImport();
            }
        }
    }

private void FireOffImport()
    {
        // Get the first file returned from the Queue folder
        string filePathSource = GetNextCSVInQueue();

        if (filePathSource != "")
        {
            // …
            // commandArguments = create our arguments here
            // …        
            // Move the file to processing folder here
            // … 

            // Give a new process the import tool location and arguments
            ProcessStartInfo startInfo = new ProcessStartInfo(importLocation + "\import.exe", commandArguments);
            try
            {
                Process process = Process.Start(startInfo);
                // process.WaitForExit(20000);
                // If the process has exited, there will be 4 csv files created in the same directory as the file.                  
            }
            catch (Exception ex)
            {
               // Deal with exception here
            }
       }
    }

我还尝试创建一个任务数组,并且 运行 这些是异步的。但最后我仍然不得不调用 Task.WaitAll() 才能读取结果。所以即使一个人提前完成,它也必须等待最长的 运行 任务。

我想我需要尝试循环创建异步进程,也许是使用任务,但我不明白如何将其作为后台进程执行此操作,这样我就可以让服务计时器检查进程数,如果它需要创造更多。

首先想到的代码改进是删除计时器并将其替换为 System.IO.FileSystemWatcherCreated 事件的事件处理程序。这样,您的代码就不需要管理哪些文件之前在队列中以及哪些新文件已经到达。更少的代码 = 更少的问题,通常。

其次,认真对待"Task"这个词暗示在1个System.IO.Tasks.Task实例中真正执行完整的导入任务,包括产生相应的导入流程实例并等待它完成后退出.

如果您希望在任何时候限制导入进程的数量 运行ning,您的代码所做的簿记的替代方法是将调度程序替换为 limits the amount of tasks,与默认调度程序相比,允许 运行 并行。如果每个任务与 1 个导入程序实例相关联,并且最多允许 N 个任务同时 运行,则您最多有 N 个导入程序实例。

下面的代码(以控制台应用程序的形式)显示了上面描述的样子,减去了提供的 link 中涵盖的自定义调度程序。

using System.Threading.Tasks;

namespace ConsoleApplication4
{
    class Program
    {
        static string importerProcessName = "import.exe";
        static string RootFolder = @"E:\temp\A\";
        static string queuePath = System.IO.Path.Combine(RootFolder, "Queue" );
        static string processingPath = System.IO.Path.Combine(RootFolder, "Processing");
        static string donePath = System.IO.Path.Combine(RootFolder, "Done");
        static void Main(string[] args)
        {
            GrantFolders(); // Make sure we have all our folders ready for action...
            var watcher = new System.IO.FileSystemWatcher(queuePath, "*.txt");
            watcher.Created += watcher_Created;
            watcher.EnableRaisingEvents = true;
            System.Console.ReadLine();
        }
        static Task ProcessFile( string fileName )
        {
            Task task = new Task(() =>
            {
                System.Console.WriteLine("Processing: " + fileName);
                System.IO.File.Move(System.IO.Path.Combine(queuePath, fileName), System.IO.Path.Combine(processingPath, fileName));
                string commandLine = "-import " + System.IO.Path.Combine(processingPath, fileName);
                using (var importer = new System.Diagnostics.Process())
                {
                    importer.StartInfo = new System.Diagnostics.ProcessStartInfo(importerProcessName, commandLine);
                    importer.Start();
                    importer.WaitForExit(20000);
                    System.IO.File.Move(System.IO.Path.Combine(processingPath, fileName), System.IO.Path.Combine(donePath, fileName));
                    System.Console.WriteLine("Done with: " + fileName);
                }
            });
            return task;
        }
        static void watcher_Created(object sender, System.IO.FileSystemEventArgs e)
        {
            System.Console.WriteLine("Found in queue: " + e.Name);
            var task = ProcessFile(e.Name);
            task.Start();
        }

        private static void GrantFolders()
        {
            string[] paths = new string[] { queuePath, processingPath, donePath };
            foreach( var path in paths)
            {
                if(!System.IO.Directory.Exists(path))
                {
                    System.IO.Directory.CreateDirectory(path);
                }
            }
        }
    }
}