我正在构建一个UWP应用程序,它从文件夹中获取文件列表,对它们进行一些处理,然后删除文件。
这很好用:
List<StorageFile> files;
public MainPage()
{
this.InitializeComponent();
files = new List<StorageFile>();
}
private async Task<List<StorageFile>> GetFiles(StorageFolder folder)
{
var items = await folder.GetItemsAsync();
foreach (var item in items)
{
if (item.GetType() == typeof(StorageFile))
files.Add(item);
else
await GetFiles(item as StorageFolder);
}
return files;
}
private async void GetFilesBtn_Click(object sender, RoutedEventArgs e)
{
// opening folder picker, then selecting a folder
var files = await GetFiles(folder);
// process files
ProcessFiles(files);
// dispose
DisposeFiles(files);
}
然而,当处理大量文件时,内存消耗非常高(显然)。
所以我想到的是使用yield return file
并处理每个文件,然后一旦我完成了该文件,我就可以处理它,并开始处理下一个文件,所以上。
我试图做的是:
public async Task<IEnumerable<StorageFile>> GetFiles(StorageFolder folder)
{
var items = await folder.GetItemsAsync();
foreach (var item in items)
{
if (item.GetType() == typeof(StorageFile))
yield return item;
else
await GetFiles(item as StorageFolder);
}
}
然后:
foreach (var file in GetFiles(folder))
{
// process file
ProcessFile(file);
// dispose
DisposeFile(file);
}
这样做的时候我得到了:
GetFiles(StorageFolder)的主体&#39;不能成为迭代器块,因为任务IEnumerable StorageFile不是迭代器接口 类型。
我之前从未使用过yield return
,所以我不确定如何做到这一点。
答案 0 :(得分:2)
这是去Reactive的绝佳机会!
我创建了这个简单的程序,您可以轻松编辑该程序以使用StorageFolder和StorageFile而不是字符串作为路径:
class Program
{
static void Main(string[] args)
{
Task.Run(async () =>
{
GetFilesFromDirectory(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)).Subscribe(
file =>
{
Console.WriteLine(file);
});
var files = await GetFilesFromDirectory(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData))
.ToArray(); // you can also do this
foreach (var file in files)
{
Console.WriteLine(file);
}
Console.ReadLine();
}).Wait();
}
static IObservable<string> GetFilesFromDirectory(string path)
{
var files = new Subject<string>();
var directories = new Subject<string>();
directories.Select(x => new DirectoryInfo(x)).Subscribe(dir =>
{
foreach (var fileInfo in dir.GetFiles())
{
files.OnNext(fileInfo.FullName);
}
foreach (var directoryInfo in dir.GetDirectories())
{
directories.OnNext(directoryInfo.FullName);
}
}, () =>
{
files.OnCompleted();
});
Task.Run(() =>
{
directories.OnNext(path);
directories.OnCompleted();
});
return files;
}
Directory.GetFiles还有一个重载,它会递归搜索你:
var fileList = new DirectoryInfo(sDir).GetFiles("*", SearchOption.AllDirectories);
答案 1 :(得分:1)
你肯定知道如何让自己的生活变得困难 - 异步,屈服和递归! 不幸的是,async / await和yield目前在dotnet中不兼容。
我建议采用不同的方法,而不是让你的递归函数建立一个列表,传递一个动作,以便它适用于每个文件,如:
aaab
您可能希望使ProcessFile和DisposeFile异步,在这种情况下:
public async Task<IEnumerable<StorageFile>> ProcessFiles(StorageFolder folder, Action<StorageFile> process)
{
var items = await folder.GetItemsAsync();
foreach (var item in items)
{
if (item.GetType() == typeof(StorageFile))
process(item);
else
await ProcessFiles(item as StorageFolder);
}
}
ProcessFiles(folder, file => {
ProcessFile(file);
DisposeFile(file);
});
如果您想单独定义动作,可以这样做:
ProcessFiles(folder, async file => {
await ProcessFile(file);
await DisposeFile(file);
});
答案 2 :(得分:1)
async
关键字告诉C#编译器将该方法重写为异步工作的状态机(又名Task
)。
yield return
关键字告诉C#编译器将方法重写为一个懒惰地生成结果的状态机(又名Enumerator
)。
你要做的是将两种方法结合起来,这会使C#编译器非常难过,因为它目前无法决定如何从一种方法生成两种状态机。在dotnet / csharplang的C#中有一个未解决的问题:Champion "Async Streams" (including async disposable)
您可以使用不同的方法Task.WhenAll
,在此问题中进行了描述:Is it possible to "await yield return DoSomethingAsync()"
请注意,Task.WhenAll
会将所有中间结果解析到内存中,因此如果您不小心,最终可能会遇到比原始解决方案更复杂且耗费更多内存的解决方案。
接下来,您的代码中也存在错误:
foreach (var item in items)
{
if (item.GetType() == typeof(StorageFile))
yield return item;
else
await GetFiles(item as StorageFolder); // <---- no return here
}
在else
分支中,您不会返回检索到的值。因此,即使编译了该代码,您很快就会发现它无法正常工作。
但是,如果您添加了return
,则您的方法既需要为StorageFile
生成状态机,也要在StorageFolder
的情况下返回整个序列。这是不可能的,您需要采用一种称为展平的不同方法,只需添加另一个foreach
(请注意,为了简单起见,异步性已被删除):
foreach (var item in items)
{
if (item.GetType() == typeof(StorageFile))
{
yield return item;
}
else
{
foreach (var file in GetFiles(item as StorageFolder))
{
yield return file;
}
}
}
答案 3 :(得分:1)
从 C# 8 开始,现在可以使用 IAsyncEnumerable
来完成。
您只需将返回类型从 Task<IEnumerable<StorageFile>>
更改为 IAsyncEnumerable<StorageFile>
,然后使用 await foreach
而不是 foreach
调用该方法。
所以你的例子现在看起来像这样:
public async IAsyncEnumerable<StorageFile> GetFiles(StorageFolder folder)
{
var items = await folder.GetItemsAsync();
foreach (var item in items)
{
if (item.GetType() == typeof(StorageFile))
yield return item;
else
await foreach (var item2 in GetFiles(item as StorageFolder))
yield return item2;
}
}
那么:
await foreach (var file in GetFiles(folder))
{
// process file
ProcessFile(file);
// dispose
DisposeFile(file);
}
答案 4 :(得分:0)
我有一些类似的使用yield的例子。基本上将GetRandomStringWDelayAsync替换为您的GetFile代码。锁定不是必需的。
object lockObj = new object();
for (int i = 0; i < callCount; i++)
{
int j = i; // because of scope we can't use "i"
yield return Task.Run(async delegate {
var pair = await StringService.RandomValues.GetRandomStringWDelayAsync(j);
if (pair.Value != null)
{
lock (lockObj)
dictionary[j] = pair.Value;
}
});
}
完整代码在这里:https://github.com/sergeklokov/AsynchronousTasksDemo
答案 5 :(得分:-1)
由于yield
支持其返回类型为IEnumerable<T>
且GetFiles()
返回Task<IEnumerable<T>>
的方法,
看看这个:
public IEnumerable<StorageFile> GetFiles(StorageFolder folder)
{
// your code here
}
public Task<IEnumerable<StorageFile>> GetFilesAsync(StorageFolder folder)
{
return Task.Run(() => GetFiles(folder));
}
然后你可以执行foreach (var file in GetFiles(folder))
或foreach (var file in await GetFilesAsync(folder))
编辑: 啊,你在谈论IAsyncEnumerable,这在你发布答案的日期不存在