2013-02-05 115 views
5

我想讓我的頭在.NET 4.5中的變化,主要是異步功能。爲了解決這個問題,我想我會創建一個應用程序來歸檔我的海量照片集。我通過這樣做最好地學習了這個應用程序,它有兩個目的。C#異步文件傳輸 - 在繼續循環之前等待

我已經閱讀了大量關於使用異步的MSDN文章,但我不認爲我有足夠的理解它(因爲它不工作)。我的意圖是將源文件夾中的每張照片根據其拍攝日期複製到目標文件夾(或者如果採用元數據丟失,則創建)。同時將其重命名爲標準命名約定,並在圖像存檔在圖像框中時顯示圖像。我希望應用程序在工作期間保持響應,這是異步進入的地方。現在應用程序的目的並不重要,整個過程讓我的頭腦變得異常。

實際發生的情況是應用程序無響應,將所有圖像歸檔爲預期,但圖像框僅顯示最終圖像。異步正在開始文件傳輸,然後移動到下一個圖像,開始傳輸然後繼續等等等等,所以我最終成百上千的開放文件流,而不是等待每個關閉。

任何指針在哪裏我錯了將不勝感激。我對使用Tasks的理解是shakey,返回任務有什麼用途?

imgMain是XAML文件中的圖像框。異步/等待處於歸檔方法中,但顯示所有代碼可能相關。

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

我不是專門研究.Net 4.5的新異步/等待功能的專家,但有一點非常重要,那就是您異步運行的唯一一件事是文件副本。雖然有更好的指導,但我相信你會得到一些有用的答案。 –

+0

@DanielKelley你還想要異步運行嗎? – svick

+0

@svick根據你的答案 - 存檔中的所有內容。除了'等待'fs.CopyToAsync'之外,其他所有事情都在捆綁UI線程。 –

回答

6

我對使用Tasks的理解是shakey,返回一個任務有什麼用途?

Task表示異步操作。當Task完成時,表示操作已完成。你可以awaitTask,這意味着你將異步等待它完成(不會阻塞UI線程)。

但是,如果你使你的方法async void,沒有辦法等待操作完成。當方法返回時,您知道異步操作已啓動,但就是這樣。

您需要做的是將Archive()更改爲返回Task,以便您可以等待它在事件處理程序中完成。 Task將自動返回,您不需要(或可以)添加任何return s。

因此,改變Archive()到簽名:

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

然後await它在你的事件處理程序(你還需要改變async):

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

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

一般來說,async void方法應該只用於事件處理程序的。所有其他async方法應該是async Task(或async Task<SomeType>如果它們返回一些值),這樣你就可以await它們。

+0

你忘了更新'Archive'簽名嗎? (從我+1作爲一個很好的明確解釋) –

+0

@DanielKelley是的,我做到了。謝謝,現在修復。 – svick

+0

很好的解釋,現在就測試一下。我認爲週末閱讀可能需要更好地瞭解任務可以做什麼。 – James

0

你需要等待Archive方法,因爲你只想要Archive方法的一個實例,以在任何時間單點運行。請注意,在你的實現中,你啓動了很多Archive實例,並沒有真正釋放UI線程。

修改您的代碼:

  • 添加asyncbtnSort_Click
  • 添加返回類型TaskArchive
  • 恭候ArchivebtnSort_Click

提示: 如果調用的第一個方法(在你的情況下btnSort_Click)是不是異步的,它不會被視爲「從外部」的異步,即你的窗口和UI線程。

+0

你不能'等待'一個'async void'方法。 – svick

+0

我曾嘗試過,但是等待會拋出錯誤'void is not awaitable'。我認爲這是我對任務缺乏理解的地方。存檔方法需要返回一個任務?儘管目的是什麼?歡呼 – James

+0

@svick,這確實是正確的,任務是必需的。 – flindeberg

相關問題