使用IAsyncEnumerable时先调用StartQueryingFromFiles致参数为空求助
问题描述
基于soundfingerprinting库尝试用IAsyncEnumerable<string>读取文件夹所有文件路径,但调用StartQueryingFromFiles(IAsyncEnumerable<string> files, CancellationToken token)时,该方法会在IAsyncEnumerable的迭代逻辑执行前就运行,导致传入的文件流始终为空。
按钮点击事件代码
private void btnStarts_Click(object sender, EventArgs e, Label searchLabel, TabPage page, ProgressBar bar) { try { bar.Style = ProgressBarStyle.Marquee; var searchPath = searchLabel.Text; //Path to folder var tokenSource = new CancellationTokenSource(); var bolFinished = false; if (!string.IsNullOrEmpty(searchPath) && Directory.Exists(searchPath)) { Task.Run(() => { //The method below gets called first before the iteration StartQueryingFromFiles(FetchAllItems(searchPath), tokenSource.Token); bolFinished = true; }); } else { MessageBox.Show("No Audio Files Found."); } if(bolFinished) bar.Style = ProgressBarStyle.Blocks; } catch (Exception ex) { MessageBox.Show(ex.Message); } }
文件枚举方法
//This gets called first, its supposed to return all files, but returns nothing IAsyncEnumerable<string> FetchAllItems(string searchPath) { return FetchItems(searchPath); } //This is called last, I don't know why. It reads the file paths in the folder async IAsyncEnumerable<string> FetchItems(string searchPath) { foreach (var item in Directory.EnumerateFiles(searchPath)) { await Task.Delay(100); System.Diagnostics.Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} Reading: {item}"); yield return item; } }
StartQueryingFromFiles方法
public void StartQueryingFromFiles(IAsyncEnumerable<string> files, CancellationToken token) { _ = QueryCommandBuilder.Instance .BuildRealtimeQueryCommand() .From(files, MediaType.Audio) .WithRealtimeQueryConfig(config => { // provide a success callback that will be invoked for matches that pass result entry filter config.SuccessCallback = result => { foreach (var entry in result.ResultEntries) { System.Diagnostics.Debug.WriteLine($"Successfully matched {entry.TrackId}"); } }; // configure result entry filter config.ResultEntryFilter = new TrackMatchLengthEntryFilter(5d); return config; }) .UsingServices(modelService, mediaService) .Query(token); }
问题原因与修复方案
核心问题
IAsyncEnumerable延迟执行特性:FetchItems中的迭代逻辑(yield return)只有当流被消费时才会触发,这是设计特性,但代码存在异步等待缺失,导致状态判断错误。- 未等待异步操作完成:
Task.Run内调用的StartQueryingFromFiles启动了异步Query操作,但未等待其完成就标记bolFinished = true,UI线程的状态判断会在异步任务刚启动时就执行,完全无效。 - 跨线程UI更新风险:直接在非UI线程更新进度条控件,可能引发异常。
具体修复步骤
- 将
StartQueryingFromFiles改为异步方法,等待查询操作完成:
public async Task StartQueryingFromFiles(IAsyncEnumerable<string> files, CancellationToken token) { await QueryCommandBuilder.Instance .BuildRealtimeQueryCommand() .From(files, MediaType.Audio) .WithRealtimeQueryConfig(config => { config.SuccessCallback = result => { foreach (var entry in result.ResultEntries) { System.Diagnostics.Debug.WriteLine($"Successfully matched {entry.TrackId}"); } }; config.ResultEntryFilter = new TrackMatchLengthEntryFilter(5d); return config; }) .UsingServices(modelService, mediaService) .Query(token); }
- 修改按钮点击事件,正确等待异步任务并安全更新UI:
private async void btnStarts_Click(object sender, EventArgs e, Label searchLabel, TabPage page, ProgressBar bar) { try { bar.Style = ProgressBarStyle.Marquee; var searchPath = searchLabel.Text; using var tokenSource = new CancellationTokenSource(); if (!string.IsNullOrEmpty(searchPath) && Directory.Exists(searchPath)) { await Task.Run(async () => { // 等待查询完成,确保IAsyncEnumerable流被正常消费 await StartQueryingFromFiles(FetchAllItems(searchPath), tokenSource.Token); }, tokenSource.Token); // 回到UI线程更新进度条 bar.Invoke(() => bar.Style = ProgressBarStyle.Blocks); } else { MessageBox.Show("No Audio Files Found."); } } catch (Exception ex) { MessageBox.Show(ex.Message); } }
- 简化冗余的文件枚举方法(可选):
async IAsyncEnumerable<string> FetchAllItems(string searchPath) { foreach (var item in Directory.EnumerateFiles(searchPath)) { await Task.Delay(100); System.Diagnostics.Debug.WriteLine($"{DateTime.Now.ToLongTimeString()} Reading: {item}"); yield return item; } }
关键说明
IAsyncEnumerable的迭代逻辑只有在被消费时才会执行,确保异步操作被await后,库的From方法会正常遍历文件流,不会出现空参数问题。- 所有UI控件更新必须通过
Invoke回到UI线程,避免跨线程异常。 - 使用
using管理CancellationTokenSource,确保资源正确释放。
内容的提问来源于stack exchange,提问作者Timothy Ayodele
相关产品推荐
相关产品推荐

