您好我正在编写一个WPF程序,它在ThumbnailViewer中有缩略图。我想先生成缩略图,然后异步生成每个缩略图的图像。
我不能包括所有内容,但我认为这是相关的
生成缩略图的方法。
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;
});
}
}
当我运行上面的代码时,它就像我从未在代码中添加async / await / task一样。我错过了什么吗?再一次,我想要的是让ui保持打开状态,并在GetImage运行时填充缩略图。所以我应该一次看一个。
更新:
感谢@Peregrine指出我正确的方向。我使用MVVM模式使用自定义用户控件创建了UI。在他的回答中,他使用了它并建议我使用我的viewModel。所以我做的是将一个字符串属性添加到我的viewModel并创建一个异步方法,循环所有缩略图并将我的字符串属性设置为BitmapImage并将我的UI数据绑定到该属性。因此,只要它异步更新属性,UI也会更新。
看起来好像你被BitmapImage的构造函数误导了,它可以使用Url。
如果这个操作确实足够慢以证明使用async-await模式,那么将它分成两部分会更好。
a)从网址获取数据。这是缓慢的部分 - 它的IO绑定,并将从async-await中受益最多。
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线程上进行,并且因为它相对较快,所以对此部分使用async-await没有任何好处。
假设您正在遵循MVVM模式,那么ViewModel层中不应该有任何可视元素 - 而是对每个需要的缩略图使用ImageItemVm
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;
}
}
}
然后,您可以将Image控件的source属性绑定到相应ImageItemVm的ImageBytes属性 - WPF将自动处理从字节数组到位图图像的转换。
编辑
我误解了原来的问题,但原则仍然适用。如果您创建了一个url启动文件,我的代码可能仍然有效://但我怀疑它是最有效的。
要使用本地映像文件,请使用此替换对GetBytesFromUrlAsync()的调用
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;
}
}
运行GetImages的Task几乎不做任何事情,只有Dispatcher.Invoke,即或多或少所有代码都在UI线程中运行。
更改它以便在UI线程外部创建BitmapImage,然后将其冻结以使其可跨线程访问:
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
方法,除非它是一个事件处理程序。如下所示更改它,并在调用它时等待它:
public async Task GenerateThumbnails()
{
...
await Task.Run(() => GetImages(path, pageCount));
}
要不就:
public Task GenerateThumbnails()
{
...
return Task.Run(() => GetImages(path, pageCount));
}
完全避免async
/ await
的替代方案是具有ImageSource属性的视图模型,通过在Binding上指定IsAsync
来异步调用其getter:
<Image Source="{Binding Image, IsAsync=True}"/>
使用这样的视图模型:
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;
}
}
}
而不是让调度员和来回跳跃,我会做这样的事情:
private Task<BitmapImage[]> GetImagesAsync(string path, int pageCount)
{
return Task.Run(() =>
{
var images = new BitmapImage[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();
images[i] = bitmap;
}
return images;
}
}
然后,在UI线程调用代码上:
var images = await GetImagesAsync(path, pageCount);
for (int i = 0; i < pageCount; i++)
{
this._viewModel.Thumbs[i].img.Source = images[i];
}