首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >异步程序仍然冻结UI

异步程序仍然冻结UI
EN

Stack Overflow用户
提问于 2018-09-04 19:34:58
回答 4查看 820关注 0票数 0

你好,我正在编写一个WPF程序,它在ThumbnailViewer中有缩略图。我想先生成缩略图,然后异步生成每个缩略图的图像。

我不能把所有的东西都包括进去,但我认为这是相关的

方法生成缩略图。

代码语言:javascript
复制
public async void GenerateThumbnails()
{
   // In short there is 120 thumbnails I will load.
   string path = @"C:\....\...\...png";
   int pageCount = 120;

   SetThumbnails(path, pageCount);
   await Task.Run(() => GetImages(path, pageCount);
 }

 SetThumbnails(string path, int pageCount)
 {
    for(int i = 1; i <= pageCount; i ++)
    {
        // Sets the pageNumber of the current thumbnail
        var thumb = new Thumbnail(i.ToString());
        // Add the current thumb to my thumbs which is 
        // binded to the ui
        this._viewModel.thumbs.Add(thumb);
    }
  }

  GetImages(string path, int pageCount)
  {
       for(int i = 1; i <= pageCount; i ++)
       {
            Dispatcher.Invoke(() =>
            {
                var uri = new Uri(path);
                var bitmap = new BitmapImage(uri);
                this._viewModel.Thumbs[i - 1].img.Source = bitmap;
            });
        }
  }

当我运行上面的代码时,它的工作方式就像我从未向代码中添加异步/等待/任务一样。我是不是遗漏了什么?同样,我希望ui保持打开,并且在GetImage运行时填充缩略图。所以我应该一次只看他们一次。

更新:

感谢@Peregrine为我指明了正确的方向。我使用MVVM模式使用自定义用户控件创建了UI。在他的回答中,他使用了它,并建议我使用我的viewModel。因此,我所做的是向我的viewModel添加一个string属性,并创建一个异步方法,该方法循环遍历所有缩略图,并将string属性设置为BitmapImage,并将UI数据库设置为该属性。因此,每当它异步更新属性时,UI也会更新。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2018-09-05 09:56:18

看起来,您似乎被BitmapImage的构造器误导了,该构造函数可以接受一个Url。

如果这个操作确实慢到足以证明使用异步等待模式是合理的,那么将其分为两个部分会更好。

( a)从url获取数据。这是慢的部分--它是IO绑定的,并且从异步等待中获益最大。

代码语言:javascript
复制
public static class MyIOAsync
{
    public static async Task<byte[]> GetBytesFromUrlAsync(string url)
    {
        using (var httpClient = new HttpClient())
        {
            return await httpClient
                       .GetByteArrayAsync(url)
                       .ConfigureAwait(false);
        }
    }
}

b)创建位图对象。这需要在主UI线程上进行,而且由于它的速度相对较快,所以使用异步等待对此部分没有任何好处。

假设您遵循的是MVVM模式,您不应该在ViewModel层中有任何可视元素--而是对所需的每个缩略图使用一个ImageItemVm

代码语言:javascript
复制
public class ImageItemVm : ViewModelBase
{
    public ThumbnailItemVm(string url)
    {
        Url = url;
    }

    public string Url { get; }

    private bool _fetchingBytes;

    private byte[] _imageBytes;

    public byte[] ImageBytes
    {
        get
        {
            if (_imageBytes != null || _fetchingBytes)
                return _imageBytes;

            // refresh ImageBytes once the data fetching task has completed OK
            Action<Task<byte[]>> continuation = async task =>
                {
                    _imageBytes = await task;
                    RaisePropertyChanged(nameof(ImageBytes));
                };

            // no need for await here as the continuations will handle everything
            MyIOAsync.GetBytesFromUrlAsync(Url)
                .ContinueWith(continuation, 
                              TaskContinuationOptions.OnlyOnRanToCompletion)
                .ContinueWith(_ => _fetchingBytes = false) 
                .ConfigureAwait(false);

            return null;
        }
    }
}

然后,您可以将图像控件的源属性绑定到相应的ImageBytes属性- WPF将自动处理从字节数组到位图图像的转换。

编辑

我误解了原来的问题,但这个原则仍然适用。如果您创建了一个url启动文件://,我的代码可能仍然有效,但我怀疑它是否会是最有效的。

若要使用本地映像文件,请将对GetBytesFromUrlAsync()的调用替换为

代码语言:javascript
复制
public static async Task<byte[]> ReadBytesFromFileAsync(string fileName)
{
    using (var file = new FileStream(fileName, 
                                     FileMode.Open, 
                                     FileAccess.Read, 
                                     FileShare.Read, 
                                     4096, 
                                     useAsync: true))
    {
        var bytes = new byte[file.Length];

        await file.ReadAsync(bytes, 0, (int)file.Length)
                  .ConfigureAwait(false);

        return bytes;
    }
}
票数 0
EN

Stack Overflow用户

发布于 2018-09-04 19:45:57

运行GetImages的任务实际上只执行Dispatcher.Invoke,也就是说,您的所有代码或多或少都运行在UI线程中。

更改它,以便在UI线程之外创建BitmapImage,然后冻结它,使其可以跨线程访问:

代码语言:javascript
复制
private void GetImages(string path, int pageCount)
{
    for (int i = 0; i < pageCount; i++)
    {
        var bitmap = new BitmapImage();
        bitmap.BeginInit();
        bitmap.CacheOption = BitmapCacheOption.OnLoad;
        bitmap.UriSource = new Uri(path);
        bitmap.EndInit();
        bitmap.Freeze();

        Dispatcher.Invoke(() => this._viewModel.Thumbs[i].img.Source = bitmap);
    }
}

您还应该避免任何async void方法,特别是当它是事件处理程序时。按如下所示更改它,并在调用它时等待它:

代码语言:javascript
复制
public async Task GenerateThumbnails()
{
    ...
    await Task.Run(() => GetImages(path, pageCount));
}

或者只是:

代码语言:javascript
复制
public Task GenerateThumbnails()
{
    ...
    return Task.Run(() => GetImages(path, pageCount));
}
票数 1
EN

Stack Overflow用户

发布于 2018-09-06 22:39:54

完全避免async/await的另一种方法是具有ImageSource属性的视图模型,通过在绑定上指定IsAsync来异步调用getter:

代码语言:javascript
复制
<Image Source="{Binding Image, IsAsync=True}"/>

使用这样的视图模型:

代码语言:javascript
复制
public class ThumbnailViewModel
{
    public ThumbnailViewModel(string path)
    {
        Path = path;
    }

    public string Path { get; }

    private BitmapImage îmage;

    public BitmapImage Image
    {
        get
        {
            if (îmage == null)
            {
                îmage = new BitmapImage();
                îmage.BeginInit();
                îmage.CacheOption = BitmapCacheOption.OnLoad;
                îmage.UriSource = new Uri(Path);
                îmage.EndInit();
                îmage.Freeze();
            }

            return îmage;
        }
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52172918

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档