为什么这种异步方法会锁定用户界面

本文关键字:锁定 用户界面 异步方法 为什么 | 更新日期: 2023-09-27 18:19:12

我使用以下代码读取网络驱动器上的所有图像,并用每个图像填充一个ImageControl,然后将它们显示在屏幕上。

我遇到的问题是,无论PopulateImages()是否成为async方法,以及如何Task.WaitAll运行用户界面,直到所有图像呈现为止,用户界面仍然被锁定。

我做async/await不正确吗?我需要做什么来解决这个问题?

public MainWindow()
{
    InitializeComponent();
    Loaded += (s, e) => PopulateImages();
}
private async void PopulateImages()
{
    string StartDirectory = @"//path/to/network/folder";
    Task.WaitAll(Directory
        .EnumerateFiles(StartDirectory)
        .Select(filename => Task.Run(async () =>
        {
            Bitmap resizedImage;
            using (var sourceStream = File.Open(filename, FileMode.Open))
            {
                using (var destinationStream = new MemoryStream())
                {
                    await sourceStream.CopyToAsync(destinationStream);
                    resizedImage = ResizeImage(new Bitmap(destinationStream), 96, 96);
                }
            }
            Dispatcher.BeginInvoke(new Action(() =>
            {
                var imgControl = new ImageControl(filename, resizedImage);
                stackpanelContainer.Children.Add(imgControl);
            }));
        })).ToArray());
}

为什么这种异步方法会锁定用户界面

您正在使用Task.WaitAll - 它会阻止,直到所有任务都完成。

相反,您应该使用 Task.WhenAll ,它返回一个Task,当所有其他任务完成后,该本身将完成。然后,您可以等待。

await Task.WhenAll(...);

虽然说实话,除非你需要在任务全部完成后做任何事情,否则你根本不需要等待它们。

请考虑重写FrameworkElement.OnInitialized方法,而不是注册到 Loaded 事件。这样,您可以await PopulateImage,保存Task.WaitAll,并可能消除使用Task.Run的需要,如果您的ResizeImage不是太重 CPU :

public override async void OnInitialized()
{
    await PopulateImages();
    base.OnInitialized();
}
private async Task PopulateImages()
{
   string StartDirectory = @"//path/to/network/folder";
   Directory.EnumerateFiles(StartDirectory)
                   .Select(filename => async () =>
                   {
                       Bitmap resizedImage;
                       using (var sourceStream = File.Open(filename, FileMode.Open))
                       using (var destinationStream = new MemoryStream())
                       {
                           await sourceStream.CopyToAsync(destinationStream);
                           resizedImage = ResizeImage(new Bitmap(destinationStream), 96, 96);
                       }
                   }
                   var imgControl = new ImageControl(filename, resizedImage);
             stackpanelContainer.Children.Add(imgControl);
}