這個問題在這里已經有了答案: 為什么 File.ReadAllLinesAsync() 會阻塞 UI 執行緒? (2 個回答) 13 小時前關閉。
我創建了一個針對本地檔案資料庫的 WPF 應用程式,以供娛樂/練習。這個想法是物體的檔案是一個 .json 檔案,它存在于磁盤上,檔案夾充當集合。在這個實作中,我有一堆 .json 檔案,它們提供有關視頻的資料以創建一種 IMDB 克隆。
我有這堂課:
public class VideoRepository : IVideoRepository
{
public async IAsyncEnumerable<Video> EnumerateEntities()
{
foreach (var file in new DirectoryInfo(Constants.JsonDatabaseVideoCollectionPath).GetFiles())
{
var json = await File.ReadAllTextAsync(file.FullName); // This blocks
var document = JsonConvert.DeserializeObject<VideoDocument>(json); // Newtonsoft
var domainObject = VideoMapper.Map(document); // A mapper to go from the document type to the domain type
yield return domainObject;
}
// Uncommenting the below lines and commenting out the above foreach loop doesn't lock up the UI.
//await Task.Delay(5000);
//yield return new Video();
}
// Rest of class.
}
在呼叫堆疊上,通過 API 層和 UI 層,我在 ViewModel 中有一個 ICommand:
QueryCommand = new RelayCommand(async (query) => await SendQuery((string)query));
private async Task SendQuery(string query)
{
QueryStatus = "Querying...";
QueryResult.Clear();
await foreach (var video in _videoEndpoints.QueryOnTags(query))
QueryResult.Add(_mapperService.Map(video));
QueryStatus = $"{QueryResult.Count()} videos found.";
}
目標是在處理查詢時向用戶顯示訊息“正在查詢...”。但是,該訊息永遠不會顯示,并且 UI 會鎖定,直到查詢完成,此時會顯示結果訊息。
In VideoRepository, if I comment out the foreach loop and uncomment the two lines below it, the UI doesn't lock up and the 'Querying...' message gets shown for 5 seconds.
Why does that happen? Is there a way to do IO without locking up the UI/blocking?
Fortunately, if this were behind a web API and hit a real database, I probably wouldn't see this issue. I'd still like the UI to not lock up with this implementation though.
EDIT: Dupe of Why File.ReadAllLinesAsync() blocks the UI thread?
Turns out Microsoft didn't make their async method very async. Changing the IO line fixes everything:
//var json = await File.ReadAllTextAsync(file.FullName); // Bad
var json = await Task.Run(() => File.ReadAllText(file.FullName)); // Good
uj5u.com熱心網友回復:
您的目標可能是 .NET 6 之前的 .NET 版本。在這些舊版本中,檔案系統 API 沒有有效實作,甚至不是真正的異步.NET 。.NET 6 中的情況有所改進,但同步檔案系統 API 的性能仍然比異步對應的 API 更高。您的問題可以簡單地通過切換來解決:
var json = await File.ReadAllTextAsync(file.FullName);
對此:
var json = await Task.Run(() => File.ReadAllText(file.FullName));
如果你想變得花哨,你也可以通過使用這樣的自定義 LINQ 運算子來解決 UI 層的問題:
public static async IAsyncEnumerable<T> OnThreadPool<T>(
this IAsyncEnumerable<T> source,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var enumerator = await Task.Run(() => source
.GetAsyncEnumerator(cancellationToken)).ConfigureAwait(false);
try
{
while (true)
{
var (moved, current) = await Task.Run(async () =>
{
if (await enumerator.MoveNextAsync())
return (true, enumerator.Current);
else
return (false, default);
}).ConfigureAwait(false);
if (!moved) break;
yield return current;
}
}
finally
{
await Task.Run(async () => await enumerator
.DisposeAsync()).ConfigureAwait(false);
}
}
此運算子卸載到ThreadPool與列舉相關的所有操作IAsyncEnumerable<T>。它可以這樣使用:
await foreach (var video in _videoEndpoints.QueryOnTags(query).OnThreadPool())
QueryResult.Add(_mapperService.Map(video));
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/445691.html
標籤:c# wpf asynchronous io iasyncenumerable
下一篇:異步任務阻塞了UI
