2015-08-17 39 views
5

Sto scrivendo un programma che sposta file csv da una cartella "coda" a una cartella "elaborazione", quindi viene avviato un processo di terze parti chiamato import.exe prendendo il percorso del file csv come un argomento. Import.exe è un'attività a esecuzione prolungata.Esecuzione di processi esterni in modo asincrono in un servizio Windows

Ho bisogno che il programma continui a funzionare e controlla la coda per i nuovi file. Per questo motivo ho scelto un'applicazione di servizio Windows poiché sarà di lunga durata.

Il mio problema è che sono sopraffatto dalle opzioni e non riesco a capire se dovrei affrontare questo problema con background threads o parallel programming, o molto probabilmente una combinazione di entrambi.

Finora ho questo codice che è appena in esecuzione in modo sincrono. Vedrete rapidamente che al momento, sto semplicemente licenziando i processi senza comunque gestire o controllare il completamento. Ho commentato process.WaitForExit() poiché ovviamente questa è una chiamata bloccante.

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

Ho anche provato a creare una serie di attività e a eseguirle in modo asincrono. Ma alla fine dovevo ancora chiamare Task.WaitAll() prima che potessi leggere il risultato. Quindi, anche se uno finisce presto, deve attendere l'attività più lunga.

Penso di aver bisogno di provare il ciclo attraverso la creazione di processi in modo asincrono, magari usando le attività, ma non capisco come farlo come un processo in background, in modo da poter mantenere il timer del servizio di controllo per il numero di processi se necessario per creare di più.

+1

Accanto a '' Task.WaitAll() '', v'è anche '' Task.WaitAny() '' che potrebbe aiutare con il problema che hai descritto nella domanda. Il progetto generale che prenderei in considerazione per primo sarebbe che ogni task svolga un solo lavoro, inclusa la generazione di un'istanza di "import.exe" e in attesa che venga eseguita. Con il watcher del file system (ad esempio), rilevi le modifiche nella cartella e per ogni modifica (nuovo file) pianifichi un'attività. Il timer non è davvero essenziale qui a meno che tu non voglia limitare l'elaborazione e controllare solo gli intervalli invece di reagire immediatamente alle modifiche. – BitTickler

+1

Un problema da correggere: considera cosa succede se qualche altro software utilizza una procedura denominata "import.exe". (Piuttosto che contare il numero di processi con un determinato nome, dovresti tenere traccia dei processi specifici che hai lanciato.) –

+0

Thats @HarryJohnston.Con la risposta data non credo di aver bisogno di tenere traccia del numero di processi in esecuzione più. Se avessi continuato con il codice nella mia domanda, terrei una lista degli ID di processo. Assicurarsi di rimuoverli dall'elenco quando il processo è terminato. – SeanOB

risposta

5

Il primo miglioramento del codice che viene in mente sarebbe quello di rimuovere il timer e sostituirlo con un System.IO.FileSystemWatcher e un gestore di eventi per l'evento Created. In questo modo, il tuo codice non ha bisogno di amministrare quali file erano in coda prima e quali nuovi sono arrivati. Meno codice = meno problemi, di solito.

In secondo luogo, prendere la parola "Attività" suggerisce seriamente di eseguire realmente un'attività di importazione completa in 1 istanza System.IO.Tasks.Task, tra cui spawning l'istanza del processo di importazione corrispondente e in attesa di uscita quando viene eseguita.

Se si desidera limitare la quantità di processi di importazione in esecuzione in qualsiasi momento, un'alternativa della contabilità del codice, sarebbe quella di sostituire lo scheduler con uno scheduler che è limits the amount of tasks, eseguito in parallelo rispetto allo scheduler predefinito . Se ogni attività è associata a 1 istanza di importatore e le attività N massime possono essere eseguite simultaneamente, si hanno al massimo N istanze del processo di importazione.

Il codice seguente mostra (sotto forma di un'applicazione di console), come potrebbe apparire il sopra descritto, meno lo schedulatore personalizzato che è coperto nel collegamento fornito.

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

Questo sembra fantastico. Ci sto approfondendo ora, così ti farò sapere il risultato. Non sapevo che esistesse FileSystemWatcher ... Grazie mille per l'esempio completo. – SeanOB

+0

Funziona alla grande. Ho anche utilizzato la classe limite del task scheduler dal link che hai fornito e limita il numero di processi in esecuzione. Non ho implementato nessuna delle cose di fabbrica. Immagino che la lezione qui sia che, poiché non sono interessato all'elaborazione di "batch", non ho bisogno di raggruppare i miei Task in un elenco o altro (sebbene "Task.WaitAny()" abbia fatto il lavoro.) – SeanOB