我试图了解.NET 4.5中的变化,主要是异步功能。为了理解它,我想我会创建一个小应用程序来存档我的大量照片集。通过这样做,我学得最好,应用程序有双重目的。
我已经阅读了很多关于使用异步的MSDN文章,但我认为我对它没有足够的了解(因为它不起作用)。我的目的是将源文件夹中的每张照片根据其拍摄日期复制到目标文件夹(或者如果丢失了元数据则创建)。同时将其重命名为标准命名约定,并在图像框中存档时显示图像。我希望应用程序在工作期间保持响应,这就是异步进入的地方。现在应用程序的目的并不重要,整个过程都让我不知所措。
实际发生的是应用程序没有响应,按预期归档所有图像,但图像框仅显示最终图片。 Async正在开始文件传输,然后转移到下一个图像,开始传输然后继续等等,所以我最终得到数百个打开的文件流,而不是等待每个关闭。
任何有关我出错的指示都将不胜感激。我对使用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);
}
}
}
}
}
答案 0 :(得分:6)
我对使用任务的理解很简单,返回任务是为了什么目的?
Task
表示异步操作。 Task
完成后,表示操作已完成。您可以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
。
答案 1 :(得分:0)
您需要等待Archive
方法,因为您只希望在任何单个时间点运行Archive
方法的单个实例。请注意,在您的实现中,您启动了很多Archive
个实例,并没有真正发布UI线程。
对代码的修改:
async
添加到btnSort_Click
Task
添加到Archive
Archive
btnSort_Click
提示:
如果调用的第一个方法(在您的情况下为btnSort_Click
)不是异步的,则不会将其视为“来自外部”的异步,即您的窗口和UI线程。