C#异步文件传输 - 在继续循环之前等待

Jam*_*mes 5 .net c# asynchronous async-await .net-4.5

我试图了解.NET 4.5中的变化,主要是异步功能.为了理解它,我想我会创建一个小应用程序来存档我的大量照片集.我通过这样做来学习最好的应用程序有双重目的.

我已经阅读了很多关于使用异步的MSDN文章,但我认为我对它没有足够的了解(因为它不起作用).我的目的是将源文件夹中的每张照片根据其拍摄日期复制到目标文件夹(或者如果缺少所拍摄的元数据则创建).同时将其重命名为标准命名约定,并在图像框中存档时显示图像.我希望应用程序在工作期间保持响应,这是异步进入的地方.现在应用程序的目的并不重要,整个过程让我的头脑异步.

实际发生的是应用程序没有响应,按预期存档所有图像,但图像框仅显示最终图片.异步开始文件传输,然后继续下一个图像,开始传输然后继续等等,所以我最终得到数百个打开的文件流,而不是等待每个关闭.

任何我错误的地方的指针将不胜感激.我对使用任务的理解很简单,返回任务服务的目的是什么?

imgMain是XAML文件中的图像框.async/await在归档方法中,但显示所有可能相关的代码.

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


    }
}
Run Code Online (Sandbox Code Playgroud)

}

svi*_*ick 6

我对使用任务的理解很简单,返回任务服务的目的是什么?

Task是异步操作的表示.当Task完成,这意味着操作完成.你可以awaitTask,这意味着你将异步等待它完成(不阻塞UI线程).

但是如果你制作方法async void,就没有办法等待操作完成.当方法返回时,您知道异步操作已经启动,但就是这样.

您需要做的是更改Archive()为返回a Task,以便您可以等待它在事件处理程序中完成.在Task将自动返回,你并不需要(或可以)添加任何return秒.

因此,将签名更改Archive()为:

private async Task Archive(string file, string destination, DateTime taken)
Run Code Online (Sandbox Code Playgroud)

然后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);
    }
}
Run Code Online (Sandbox Code Playgroud)

通常,async void方法应用于事件处理程序.所有其他async方法应该是async Task(或者async Task<SomeType>如果它们返回一些值),那么你就可以await了.