【问题标题】:C# async file transfer - waiting before continuing loopC# 异步文件传输 - 在继续循环之前等待
【发布时间】:2013-02-05 10:53:25
【问题描述】:

我试图了解 .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);
            }
        }


    }
}

}

【问题讨论】:

  • 我不是 .Net 4.5 的新异步/等待功能方面的专家,但对我来说确实很突出的一件事是,您唯一异步运行的是文件副本。我相信你会得到一些有用的答案,尽管有更好的指导。
  • @DanielKelley 您还想异步运行什么?
  • @svick 根据您的回答-存档中的所有内容。除了 awaiting fs.CopyToAsync 之外,其他一切都在占用 UI 线程。
  • @DanielKelley 它仍然是,即使你 awaitArchive() 的结果。但是: 1. 应该没问题,里面看不到长的操作。 2. 这些操作没有异步版本。
  • @svick 也许我的评论措辞不当——我并不是说每个操作都应该使用异步方法执行(例如文件删除)。我的意思是他正在等待文件副本,而不是他对Archive 的调用——由于方法的签名不正确,他不能这样做。

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


【解决方案1】:

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

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

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

您需要做的是将Archive() 更改为返回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&lt;SomeType&gt;,如果它们返回一些值),这样你就可以await它们。

【讨论】:

  • 你忘记更新Archive的签名了吗? (我的+1作为一个很好的清晰解释)
  • @DanielKelley 是的,我做到了。谢谢,现在修好了。
  • 很好的解释,现在只是测试一下。我认为可能需要周末阅读以更好地了解 Tasks 可以做什么。
  • 效果很好。现在做了我所期望的。非常感谢。
【解决方案2】:

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

对代码的修改:

  • async 添加到btnSort_Click
  • 将返回类型Task 添加到Archive
  • btnSort_Click 中等待Archive

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

【讨论】:

  • 你不能 await 一个 async void 方法。
  • 我试过了,但是 await 会抛出错误'void is not awaitable'。我想这就是我对任务缺乏理解的原因。 Archive 方法需要返回一个任务吗?还是为了什么目的?干杯
  • @James 抱歉这个错误,返回类型必须是 Task 才能“等待”。
猜你喜欢
  • 1970-01-01
  • 2017-11-14
  • 2020-07-14
  • 1970-01-01
  • 2011-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-19
相关资源
最近更新 更多