【问题标题】:Running external processes asynchronously in a windows service在 Windows 服务中异步运行外部进程
【发布时间】:2015-08-17 00:33:10
【问题描述】:

我正在编写一个程序,将 csv 文件从“队列”文件夹移动到“处理”文件夹,然后启动名为 import.exe 的第三方进程,将 csv 文件路径作为参数。 Import.exe 是一项长期运行的任务。

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

我的问题是我被各种选项压得喘不过气来,不知道我应该使用background threads 还是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() 才能读取结果。因此,即使提前完成,它也必须等待运行时间最长的任务。

我想我需要尝试循环通过异步创建进程可能使用任务,但我不明白如何将其作为后台进程执行,以便我可以让服务计时器在需要时检查进程数创造更多。

【问题讨论】:

  • Task.WaitAll() 旁边,还有Task.WaitAny() 可以帮助您解决问题中描述的问题。我首先考虑的一般设计是,每个任务都完成一项工作,包括生成import.exe 的实例并等待它完成。使用您的文件系统观察程序(例如),您可以检测文件夹中的更改,并为每个更改(新文件)安排一个任务。除非您想限制处理并仅检查间隔而不是立即对更改做出反应,否则计时器在这里并不是必需的。
  • 您应该更正的一个问题:考虑如果其他一些软件碰巧使用名为“import.exe”的进程会发生什么。 (而不是计算具有给定名称的进程数,您应该跟踪已启动的特定进程。)
  • 那是@HarryJohnston。有了给出的答案,我认为我不需要再跟踪正在运行的进程数量了。如果我继续使用问题中的代码,我会保留一个进程 ID 列表。确保在进程退出时将它们从列表中删除。
  • 是的,接受的答案隐含地解决了这个问题 - 您通过将每个过程与单独的任务相关联来跟踪过程。 :-)

标签: c# multithreading asynchronous windows-services .net-4.5


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 这看起来很棒。我现在正在研究它,所以会让你知道结果。我不知道 FileSystemWatcher 存在...非常感谢完整的示例。
  • 这很好用。我还使用了您提供的链接中的任务调度程序限制类,并很好地限制了正在运行的进程的数量。我没有实现任何工厂的东西。我想这里的教训是,因为我不关心处理“批次”,我不需要将我的任务捆绑在一个列表或任何东西中(尽管Task.WaitAny() 确实完成了工作。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多