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

标签 c# .net asynchronous .net-4.5 async-await

我正在努力了解 .NET 4.5 中的变化,主要是异步功能。为了解决这个问题,我想我会创建一个小应用程序来存档我的大量照片集。通过这样做我学得最好,该应用程序有双重用途。

我已经阅读了大量关于使用异步的 MSDN 文章,但我认为我对它的理解不够好(因为它不起作用)。我的意图是将源文件夹中的每张照片根据其拍摄日期(或者如果拍摄的元数据丢失则创建)复制到目标文件夹。同时将其重命名为标准命名约定,并在图像框中显示存档图像。我希望应用程序在工作期间保持响应,这就是异步的用武之地。现在应用程序的目的并不重要,重点是让我了解异步。

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

如能指出我出错的地方,我们将不胜感激。我对使用 Tasks 的理解是不稳定的,返回一个任务有什么用?

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


    }
}

最佳答案

My understanding of using Tasks is shakey, returning a task serves what purpose?

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

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

你需要做的是改变Archive()返回 Task ,这样您就可以在事件处理程序中等待它完成。 Task将自动返回,您不需要(或可以)添加任何 return

所以,更改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他们。

关于C# 异步文件传输 - 在继续循环之前等待,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14705803/

相关文章:

c# - 如何使 [DebuggerNonUserCode] 在简单测试用例中隐藏调试器的异常?

c# - Nuget 将 web.config 重命名为 web.config.transform

c# - 我应该在 DuplexClientBase 构造函数的 "endpointConfigurationName"中指定什么?

c# - 如何使用 Border() 仅显示网格(而非列)的行边框

javascript - 在 map 中调用异步函数的最佳方法?

c# - 为什么 IEnumerable 慢而 List 快?

c# - ajax参数总是为空?

c# - 代码分析规则 CA1062 行为

javascript - node.js:将异步函数列表应用于列表列表中的每个项目

node.js - 在 Node.js 中使用 Promise 会使 Node 同步吗?