我正在努力了解 .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/