2013-02-05 4 views
5

Ich versuche, die Änderungen in .NET 4.5, hauptsächlich die asynchronen Funktionen, zu verstehen. Um mich daran zu gewöhnen, dachte ich, ich würde eine kleine App erstellen, um meine riesige Fotosammlung zu archivieren. Ich lerne am besten damit, dass die Anwendung einen doppelten Zweck erfüllt.C# asynchrone Dateiübertragung - Warten vor dem Fortsetzen der Schleife

Ich habe viele MSDN Artikel über die Verwendung von Async gelesen, aber ich glaube nicht, dass ich ein gutes Verständnis davon habe (weil es nicht funktioniert). Ich wollte, dass jedes Foto in einem Quellordner basierend auf seinem Aufnahmedatum in einen Zielordner kopiert wird (oder erstellt wird, wenn die übernommenen Metadaten fehlen). Gleichzeitig wird es in eine Standardbenennungskonvention umbenannt und das Bild wird so angezeigt, wie es in einer Bildbox archiviert ist. Ich wollte, dass die Anwendung während der Arbeit immer wieder antwortet. An dieser Stelle kommt Async ins Spiel. Jetzt ist der App-Zweck unwichtig, der ganze Punkt brachte mich um den Async herum.

Was tatsächlich passiert, ist die App reagiert nicht mehr, archiviert alle Bilder wie vorgesehen, aber die Bildbox zeigt nur das endgültige Bild. Async startet die Dateiübertragung, geht dann zum nächsten Bild über, startet die Übertragung und geht dann weiter usw. usw., so dass ich am Ende Hunderte von offenen Dateiströmen habe, anstatt darauf zu warten, dass alle schließen.

Irgendwelche Hinweise, in denen ich falsch liege, würden geschätzt werden. Mein Verständnis von der Verwendung von Tasks ist shakey, die Rückgabe einer Aufgabe dient welchem ​​Zweck?

imgMain ist die Bildbox in der XAML-Datei. Das async/await ist in der Archivierungsmethode, zeigt aber den gesamten Code an, der relevant sein könnte.

using System; 
using System.Drawing.Imaging; 
using System.Linq; 
using System.Text; 
using System.Text.RegularExpressions; 
using System.Windows; 
using System.Windows.Media.Imaging; 
using System.Windows.Forms; 
using System.IO; 

namespace PhotoArchive 
{ 
/// <summary> 
/// Interaction logic for MainWindow.xaml 
/// </summary> 
public partial class MainWindow : Window 
{ 

    private string Source 
    { 
     get { return txtSource.Text; } 
     set { txtSource.Text = value; } 
    } 

    private string Destination 
    { 
     get { return txtDestination.Text; } 
     set { txtDestination.Text = value; } 
    } 


    public MainWindow() 
    { 
     InitializeComponent(); 

    } 

    private void btnBrowseDataSource_Click(object sender, RoutedEventArgs e) 
    { 
     var dialogue = new FolderBrowserDialog(); 
     dialogue.ShowDialog(); 
     Source = dialogue.SelectedPath; 

    } 

    private void btnBrowseDestination_Click(object sender, RoutedEventArgs e) 
    { 
     var dialogue = new FolderBrowserDialog(); 
     dialogue.ShowDialog(); 
     Destination= dialogue.SelectedPath; 
    } 

    private void btnSort_Click(object sender, RoutedEventArgs e) 
    { 
     var files = Directory.GetFiles(Source, "*.*", SearchOption.AllDirectories); 
     var result = from i in files 
        where i.ToLower().Contains(".jpg") || i.ToLower().Contains(".jpeg") || i.ToLower().Contains(".png") 
        select i; 


     foreach (string f in result) 
     { 
      DateTime dest = GetDateTakenFromImage(f); 
      Archive(f, Destination, dest); 
     } 

    } 

    private async void Archive(string file, string destination, DateTime taken) 
    { 

     //Find Destination Path 
     var sb = new StringBuilder(); 
     sb.Append(destination); 
     sb.Append("\\"); 
     sb.Append(taken.ToString("yyyy")); 
     sb.Append("\\"); 
     sb.Append(taken.ToString("MM")); 
     sb.Append("\\"); 

     if (! Directory.Exists(sb.ToString())) 
     { 
      Directory.CreateDirectory(sb.ToString()); 
     } 

     sb.Append(taken.ToString("dd_MM_yyyy_H_mm_ss_")); 
     sb.Append((Directory.GetFiles(destination, "*.*", SearchOption.AllDirectories).Count())); 
     string[] extension = file.Split('.'); 
     sb.Append("." + extension[extension.Length-1]); 


     using (FileStream fs = File.Open(file, FileMode.Open)) 
     using (FileStream ds = File.Create(sb.ToString())) 
     { 
      await fs.CopyToAsync(ds); 
      fs.Close(); 
      File.Delete(file); 
     } 

     ImgMain.Source = new BitmapImage(new Uri(sb.ToString())); 
    } 

    //get date info 
    private static Regex r = new Regex(":"); 

    public static DateTime GetDateTakenFromImage(string path) 
    { 
     using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) 
     { 
      using (System.Drawing.Image img = System.Drawing.Image.FromStream(fs, false, false)) 
      { 
       PropertyItem prop; 

       try 
       { 

        prop = img.GetPropertyItem(36867); 

       } 
       catch (Exception) 
       { 
        prop = img.GetPropertyItem(306); 
       } 

       string dateTaken = r.Replace(Encoding.UTF8.GetString(prop.Value), "-", 2); 
       return DateTime.Parse(dateTaken); 
      } 
     } 


    } 
} 

}

+0

Ich bin kein Experte für die neuen async/await-Funktionen von .Net 4.5, aber eine Sache, die mir auffällt, ist, dass das einzige, was Sie asynchron ausführen, die Dateikopie ist. Ich bin sicher, dass Sie mit besserer Anleitung einige nützliche Antworten bekommen werden. –

+0

@DanielKelley Was möchten Sie sonst asynchron ausführen? – svick

+0

@svick Nach Ihrer Antwort - alles innerhalb von Archiv. Abgesehen von "erwarten" 'fs.CopyToAsync' hat alles andere den UI-Thread gebunden. –

Antwort

6

Mein Verständnis der Verwendung von Tasks ist shakey, die Rückgabe einer Aufgabe dient welchem ​​Zweck?

Die Task ist eine Darstellung der asynchronen Operation. Wenn die Task abgeschlossen ist, bedeutet dies, dass die Operation abgeschlossen ist. Und Sie können await die Task, was bedeutet, dass Sie asynchron warten, bis es abgeschlossen ist (nicht den UI-Thread blockiert).

Aber wenn Sie Ihre Methode async void machen, gibt es keine Möglichkeit zu warten, bis der Vorgang abgeschlossen ist. Wenn die Methode zurückkehrt, wissen Sie, dass die asynchrone Operation gestartet wurde, aber das war's.

Was Sie tun müssen, ist Archive() zu ändern, um eine Task zurückzugeben, so dass Sie warten können, bis es in Ihrem Event-Handler abgeschlossen ist. Die Task wird automatisch zurückgegeben, Sie müssen keine return s hinzufügen (oder hinzufügen).

Also, die Unterschrift von Archive() ändern:

private async Task Archive(string file, string destination, DateTime taken) 

Und dann await es in Ihren Event-Handler (die Sie auch zu async ändern müssen):

private async void btnSort_Click(object sender, RoutedEventArgs e) 
{ 
    // snip 

    foreach (string f in result) 
    { 
     DateTime dest = GetDateTakenFromImage(f); 
     await Archive(f, Destination, dest); 
    } 
} 

Im Allgemeinen async void Methoden sollten nur für Event-Handler verwendet werden. Alle anderen async Methoden sollten async Task (oder async Task<SomeType>, wenn sie einen Wert zurückgeben), so dass Sie await ihnen können.

+0

Haben Sie vergessen, die Signatur von 'Archive' zu ​​aktualisieren? (+1 von mir als eine nette klare Erklärung) –

+0

@DanielKelley Ja, tat ich. Danke, jetzt behoben. – svick

+0

Großartige Erklärung, nur um es jetzt zu testen. Ich denke, dass Wochenendlektüre benötigt wird, um besser zu verstehen, was Aufgaben tun können. – James

0

Sie benötigen die Archive Methode zu erwarten, da Sie nur eine einzige Instanz der Archive Methode wollen an jedem einzelnen Zeitpunkt zu laufen. Beachten Sie, dass Sie in Ihrer Implementierung eine Menge von Archive Instanzen starten und den UI-Thread nicht wirklich freigeben.

Änderungen an Ihrem Code:

  • hinzufügen async zu btnSort_Click
  • Rückgabetyp Task-Archive
  • btnSort_Click

TIP Await Archive in hinzufügen: Wenn die erste Methode aufgerufen (in Ihrem Fall btnSort_Click) ist nicht asynchron, es wird nicht als asynchron "von außen" gesehen, dh Ihr Fenster und UI-Thread.

+0

Sie können eine Async-Void-Methode nicht erwarten. – svick

+0

Ich hatte das versucht, aber das Warten wirft den Fehler 'Leere ist nicht zu erwarten'. Hier kommt mein mangelndes Verständnis von Aufgaben zum Tragen. Die Archivmethode muss eine Aufgabe zurückgeben? Zu welchem ​​Zweck? Prost – James

+0

@svick, das ist in der Tat richtig, Aufgabe ist ofc erforderlich. – flindeberg

Verwandte Themen