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

时间:2021-08-14 00:31:57

I am trying to get my head around the changes in .NET 4.5, mainly the async features. To get my head around it I thought i would create a little app for archiving my massive photo collection. I learn best by doing so the application serves a double purpose.

我想了解一下。net 4.5中的变化,主要是异步特性。为了更好地理解它,我想我应该创建一个小应用来保存我的大量照片。通过这样做,我学得最好,应用程序具有双重目的。

I have read plenty MSDN articles on using async but I don't think I have a good enough understanding of it (because it's not working). My intention was to have each photo at a source folder copied to a destination folder based on its date taken (or created if taken meta data is missing). At the same time renaming it to a standard naming convention and showing the image as it is archived in an image box. I wanted the application to keep responding during the work, which is where async comes in. Now the app purpose is unimportant, the entire point was getting my head around async.

我已经阅读了大量关于使用异步的MSDN文章,但是我认为我对异步还没有足够的理解(因为它不工作)。我的意图是将每个照片放在一个源文件夹中,并根据其日期复制到目标文件夹(或者在丢失了元数据时创建)。同时将其重命名为标准命名约定,并在图像框中显示图像。我希望应用程序在工作期间保持响应,这正是异步的作用所在。现在应用的目的并不重要,整个要点都是关于异步的。

What actually happens is the app goes unresponsive, archives all the images as intended but the image box only shows the final picture. Async is kicking off the file transfer then moving on to the next image, kicking off the transfer then moving on etc etc so i end up with hundreds of open file streams rather than it waiting for each to close.

实际发生的情况是,应用程序无法响应,将所有的图像存档,但图像框只显示最终的图像。异步启动文件传输,然后转移到下一个映像,启动传输,然后继续等等,这样我就得到了数百个打开的文件流,而不是等待每个文件关闭。

Any pointers in where I am going wrong would be appreciated. My understanding of using Tasks is shakey, returning a task serves what purpose?

我所犯的任何错误都将受到感激。我对使用任务的理解是shakey,返回任务有什么目的?

imgMain is the imagebox in the XAML file. The async/await is in the archive method but showing all code as it may be relevant.

imgMain是XAML文件中的imagebox。异步/等待是在存档方法中,但是显示所有的代码,因为它可能是相关的。

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


    }
}

}

}

2 个解决方案

#1


6  

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

我对使用任务的理解是shakey,返回任务有什么目的?

The Task is a representation of the async operation. When the Task completes, it means the operation completed. And you can await the Task, which means you will asynchronously wait for it to complete (not blocking the UI thread).

任务是异步操作的表示。当任务完成时,表示操作已经完成。您可以等待任务,这意味着您将异步地等待它完成(而不是阻塞UI线程)。

But if you make your method async void, there is no way to wait for the operation to complete. When the method returns, you know that the async operation was started, but that's it.

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

What you need to do is to change Archive() to return a Task, so that you can wait for it to complete in your event handler. The Task will be returned automatically, you don't need to (or can) add any returns.

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

So, change the signature of Archive() to:

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

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

And then await it in your event handler (which you also need to change to async):

然后在事件处理程序中等待它(您还需要将其更改为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);
    }
}

In general, async void methods should be used only for event handlers. All other async methods should be async Task (or async Task<SomeType> if they return some value), so that you can await them.

一般来说,异步void方法只能用于事件处理程序。所有其他异步方法都应该是异步任务(或者异步任务 ,如果它们返回某个值),以便您可以等待它们。

#2


0  

You need to await the Archive method, since you only want a single instance of the Archive method to be running at any single point in time. Note that in your implementation you are starting a lot of Archive instances and not really releasing the UI-thread.

您需要等待归档方法,因为您只希望归档方法的单个实例在任何时间点上运行。注意,在实现中,您正在启动许多归档实例,而不是真正释放ui线程。

Modifications to your code:

修改代码:

  • Add async to btnSort_Click
  • 添加异步btnSort_Click
  • Add return type Task to Archive
  • 向存档中添加返回类型任务
  • Await Archive in btnSort_Click
  • 等待在btnSort_Click存档

TIP: If the first method called (in your case btnSort_Click) is not async, it will not be seen as async "from the outside", ie your window and UI-thread.

提示:如果调用的第一个方法(在您的例子中是btnSort_Click)不是异步的,那么它将不会被视为“从外部”异步的,即您的窗口和UI-thread。

#1


6  

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

我对使用任务的理解是shakey,返回任务有什么目的?

The Task is a representation of the async operation. When the Task completes, it means the operation completed. And you can await the Task, which means you will asynchronously wait for it to complete (not blocking the UI thread).

任务是异步操作的表示。当任务完成时,表示操作已经完成。您可以等待任务,这意味着您将异步地等待它完成(而不是阻塞UI线程)。

But if you make your method async void, there is no way to wait for the operation to complete. When the method returns, you know that the async operation was started, but that's it.

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

What you need to do is to change Archive() to return a Task, so that you can wait for it to complete in your event handler. The Task will be returned automatically, you don't need to (or can) add any returns.

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

So, change the signature of Archive() to:

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

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

And then await it in your event handler (which you also need to change to async):

然后在事件处理程序中等待它(您还需要将其更改为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);
    }
}

In general, async void methods should be used only for event handlers. All other async methods should be async Task (or async Task<SomeType> if they return some value), so that you can await them.

一般来说,异步void方法只能用于事件处理程序。所有其他异步方法都应该是异步任务(或者异步任务 ,如果它们返回某个值),以便您可以等待它们。

#2


0  

You need to await the Archive method, since you only want a single instance of the Archive method to be running at any single point in time. Note that in your implementation you are starting a lot of Archive instances and not really releasing the UI-thread.

您需要等待归档方法,因为您只希望归档方法的单个实例在任何时间点上运行。注意,在实现中,您正在启动许多归档实例,而不是真正释放ui线程。

Modifications to your code:

修改代码:

  • Add async to btnSort_Click
  • 添加异步btnSort_Click
  • Add return type Task to Archive
  • 向存档中添加返回类型任务
  • Await Archive in btnSort_Click
  • 等待在btnSort_Click存档

TIP: If the first method called (in your case btnSort_Click) is not async, it will not be seen as async "from the outside", ie your window and UI-thread.

提示:如果调用的第一个方法(在您的例子中是btnSort_Click)不是异步的,那么它将不会被视为“从外部”异步的,即您的窗口和UI-thread。