2016-05-05 15 views
2

in einem WPF-Testprojekt Ich versuche TPL-Datenfluss zu verwenden, um durch alle Unterverzeichnisse eines bestimmten übergeordneten Verzeichnisses aufzuzählen und eine Liste von Dateien mit einer bestimmten Dateierweiterung zu erstellen, z. ".xlsx" Ich benutze 2 Blöcke, den ersten dirToFilesBlock und den letzten, fileActionBlock.TPL-Datenfluss mit Rekursion nicht abgeschlossen

Um den rekursiven Effekt des Durchgehens aller Unterverzeichnisse zu erzeugen, hat der erste Block eine Verbindung zu sich selbst mit dem Link-Prädikat-Test, um festzustellen, ob das Ausgabeelement ein Verzeichnis ist. Dies ist ein Ansatz, den ich in einem Buch über Asynchronous Programming fand. Der zweite Link ist zu dem FileActionBlock, der die Datei dann zu einer Liste hinzufügt, die auf dem Link-Prädikat-Test basiert, um zu sehen, dass die Datei über die richtige Erweiterung verfügt.

Das Problem, das ich habe, ist nach dem Kick-off Dinge mit btnStart_Click, es endet nie. Das heißt, dass wir nie unterhalb des Avatars im Event-Handler ankommen, um die Nachricht "Completed" anzuzeigen. Ich verstehe, dass ich wahrscheinlich dirToFilesBlock.Complete() aufrufen muss, aber ich weiß nicht, wo im Code dies sein sollte und unter welchen Bedingungen? Ich kann es nicht nach dem ersten Post nennen, da es aufhören würde, die Verbindung zu Unterverzeichnissen wiederherzustellen. Ich habe versucht, Dinge mit den Eigenschaften InputCount und OutputCount zu tun, kam aber nicht sehr weit. Ich möchte, wenn möglich, die Struktur des Datenflusses beibehalten, da dies bedeutet, dass ich die Benutzeroberfläche auch mit jedem neuen Verzeichnis aktualisieren kann, das über den Link zurück überprüft werden muss, um dem Benutzer eine Rückmeldung zum Fortschritt zu geben.

Ich bin sehr neu zu TPL Datenfluss und jede Hilfe wird dankbar angenommen. Hier

ist der Code aus dem Code hinter Datei:

public partial class MainWindow : Window 
{ 
    TransformManyBlock<string, string> dirToFilesBlock; 
    ActionBlock<string> fileActionBlock; 
    ObservableCollection<string> files; 
    CancellationTokenSource cts; 
    CancellationToken ct; 
    public MainWindow() 
    { 
     InitializeComponent(); 

     files = new ObservableCollection<string>(); 

     lst.DataContext = files; 

     cts = new CancellationTokenSource(); 
     ct = cts.Token; 
    } 

    private Task Start(string path) 
    { 
     var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

     dirToFilesBlock = new TransformManyBlock<string, string>((Func<string, IEnumerable<string>>)(GetFileSystemItems), new ExecutionDataflowBlockOptions() { CancellationToken = ct }); 
     fileActionBlock = new ActionBlock<string>((Action<string>)ProcessFile, new ExecutionDataflowBlockOptions() {CancellationToken = ct, TaskScheduler = uiScheduler}); 

     // Order of LinkTo's important here! 
     dirToFilesBlock.LinkTo(dirToFilesBlock, new DataflowLinkOptions() { PropagateCompletion = true }, IsDirectory); 
     dirToFilesBlock.LinkTo(fileActionBlock, new DataflowLinkOptions() { PropagateCompletion = true }, IsRequiredDocType); 

     // Kick off the recursion. 
     dirToFilesBlock.Post(path); 

     return Task.WhenAll(dirToFilesBlock.Completion, fileActionBlock.Completion); 
    } 

    private bool IsDirectory(string path) 
    { 

     return Directory.Exists(path); 
    } 


    private bool IsRequiredDocType(string fileName) 
    { 
     return System.IO.Path.GetExtension(fileName) == ".xlsx"; 
    } 

    private IEnumerable<string> GetFilesInDirectory(string path) 
    { 
     // Check for cancellation with each new dir. 
     ct.ThrowIfCancellationRequested(); 

     // Check in case of Dir access problems 
     try 
     { 
      return Directory.EnumerateFileSystemEntries(path); 
     } 
     catch (Exception) 
     { 
      return Enumerable.Empty<string>(); 
     } 
    } 

    private IEnumerable<string> GetFileSystemItems(string dir) 
    { 
     return GetFilesInDirectory(dir); 
    } 

    private void ProcessFile(string fileName) 
    { 
     ct.ThrowIfCancellationRequested(); 

     files.Add(fileName); 
    } 

    private async void btnStart_Click(object sender, RoutedEventArgs e) 
    { 
     try 
     { 
      await Start(@"C:\"); 
      // Never gets here!!! 
      MessageBox.Show("Completed"); 

     } 
     catch (OperationCanceledException) 
     { 
      MessageBox.Show("Cancelled"); 

     } 
     catch (Exception) 
     { 
      MessageBox.Show("Unknown err"); 
     } 
     finally 
     { 
     } 
    } 

    private void btnCancel_Click(object sender, RoutedEventArgs e) 
    { 
     cts.Cancel(); 
    } 
} 

}

Antwort

1

Auch wenn dies ist eine alte Frage, Abschlusses in einer Datenfluß Schleife Handhabung kann immer noch ein Thema sein.

In Ihrem Fall können Sie die TransfomBlock behalten eine Zählung der Elemente noch im Flug. Dies zeigt an, dass der Block mit der Verarbeitung einer beliebigen Anzahl von Elementen beschäftigt ist. Dann rufen Sie nur Complete() an, wenn der Block nicht belegt ist und beide Puffer leer sind. Sie können weitere Informationen zum Abwickeln der Fertigstellung in einem Beitrag finden, den ich schrieb Finding Completion in a Complex Flow: Feedback Loops

public partial class MainWindow : Window { 

     TransformManyBlock<string, string> dirToFilesBlock; 
     ActionBlock<string> fileActionBlock; 
     ObservableCollection<string> files; 
     CancellationTokenSource cts; 
     CancellationToken ct; 
     public MainWindow() { 
      InitializeComponent(); 

      files = new ObservableCollection<string>(); 

      lst.DataContext = files; 

      cts = new CancellationTokenSource(); 
      ct = cts.Token; 
     } 

     private async Task Start(string path) { 
      var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

      dirToFilesBlock = new TransformManyBlock<string, string>((Func<string, IEnumerable<string>>)(GetFileSystemItems), new ExecutionDataflowBlockOptions() { CancellationToken = ct }); 
      fileActionBlock = new ActionBlock<string>((Action<string>)ProcessFile, new ExecutionDataflowBlockOptions() { CancellationToken = ct, TaskScheduler = uiScheduler }); 

      // Order of LinkTo's important here! 
      dirToFilesBlock.LinkTo(dirToFilesBlock, new DataflowLinkOptions() { PropagateCompletion = true }, IsDirectory); 
      dirToFilesBlock.LinkTo(fileActionBlock, new DataflowLinkOptions() { PropagateCompletion = true }, IsRequiredDocType); 

      // Kick off the recursion. 
      dirToFilesBlock.Post(path); 

      await ProcessingIsComplete(); 
      dirToFilesBlock.Complete(); 
      await Task.WhenAll(dirToFilesBlock.Completion, fileActionBlock.Completion); 
     } 

     private async Task ProcessingIsComplete() { 
      while (!ct.IsCancellationRequested && DirectoryToFilesBlockIsIdle()) { 
       await Task.Delay(500); 
      } 
     } 

     private bool DirectoryToFilesBlockIsIdle() { 
      return dirToFilesBlock.InputCount == 0 && 
       dirToFilesBlock.OutputCount == 0 && 
       directoriesBeingProcessed <= 0; 
     } 

     private bool IsDirectory(string path) { 
      return Directory.Exists(path); 
     } 


     private bool IsRequiredDocType(string fileName) { 
      return System.IO.Path.GetExtension(fileName) == ".xlsx"; 
     } 

     private int directoriesBeingProcessed = 0; 

     private IEnumerable<string> GetFilesInDirectory(string path) { 
      Interlocked.Increment(ref directoriesBeingProcessed) 
      // Check for cancellation with each new dir. 
      ct.ThrowIfCancellationRequested(); 

      // Check in case of Dir access problems 
      try { 
       return Directory.EnumerateFileSystemEntries(path); 
      } catch (Exception) { 
       return Enumerable.Empty<string>(); 
      } finally { 
       Interlocked.Decrement(ref directoriesBeingProcessed); 
      } 
     } 

     private IEnumerable<string> GetFileSystemItems(string dir) { 
      return GetFilesInDirectory(dir); 
     } 

     private void ProcessFile(string fileName) { 
      ct.ThrowIfCancellationRequested(); 

      files.Add(fileName); 
     } 

     private async void btnStart_Click(object sender, RoutedEventArgs e) { 
      try { 
       await Start(@"C:\"); 
       // Never gets here!!! 
       MessageBox.Show("Completed"); 

      } catch (OperationCanceledException) { 
       MessageBox.Show("Cancelled"); 

      } catch (Exception) { 
       MessageBox.Show("Unknown err"); 
      } finally { 
      } 
     } 

     private void btnCancel_Click(object sender, RoutedEventArgs e) { 
      cts.Cancel(); 
     } 
    }