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


    }
}

}

4

2 回答 2

6

我对使用任务的理解是不稳定的,返回任务有什么用?

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

但是如果你让你的方法async void,没有办法等待操作完成。当方法返回时,您知道异步操作已启动,但仅此而已。

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

因此,将签名更改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它们。

于 2013-02-05T11:35:50.813 回答
0

您需要等待该Archive方法,因为您只希望该方法的单个实例Archive在任何单个时间点运行。请注意,在您的实现中,您启动了很多Archive实例,并没有真正释放 UI 线程。

修改您的代码:

  • 添加asyncbtnSort_Click
  • 将返回类型添加TaskArchive
  • 等待Archive_btnSort_Click

提示:如果调用的第一个方法(在您的情况下btnSort_Click)不是异步的,则它不会被视为“从外部”异步,即您的窗口和 UI 线程。

于 2013-02-05T11:09:45.283 回答