I'm having problems with my image loading blocking the UI thread so my gridview is not responsive in my windows store app.
What I'm trying to do is for the images in gridview to have a binding to image property in my view model. The value of image property is set by an async method. When app starts it loads all objects, but not the actual image data. The image data is loaded when the UI virtualization kicks in and requestes the image data via the image property bound to the image control in xaml.
All this is done in an observablecollection.
Here is some code:
private ImageSource _image = null;
private String _imagePath = null;
public ImageSource Image
{
get
{
SetImageFromStorageFile().ContinueWith(OnAsyncFail, TaskContinuationOptions.OnlyOnFaulted);
return this._image;
}
}
private async Task SetImageFromStorageFile()
{
this.IsLoading = true;
if (this._image == null && this._imagePath != null)
{
this._image = await BitmapLoader.GetPreviewImageFromStorageFile(this.StorageFile); //getting the actual data here
this.OnPropertyChanged("Image");
}
this.IsLoading = false;
}
This is all working fine except that the UI becomes unresponsive when accessing the image data.
As you can see I'm calling an async method from a property, I'm just reusing the code that I call from other places. When called from other places I can use await and the UI is responsive. The problem is that when using the gridviews UI virtualization I don't know how to run this async method without blocking the UI since properties are not possible to run async (as far as I know).
So I just want the gridview to run this property (or method) async instead of sync, but don't know how to do it.
Please help :)
private ImageSource _image = null;
private String _imagePath = null;
public ImageSource Image
{
get
{
if (_image != null)
return _image;
if (_imagePath != null && !IsLoading)
SetImageFromStorageFile();
return null;
}
}
private async void SetImageFromStorageFile()
{
if (this.IsLoading || this._image != null || this._imagePath == null)
return;
this.IsLoading = true;
try
{
this._image = await BitmapLoader.GetPreviewImageFromStorageFile(this.StorageFile); //getting the actual data here
this.IsLoading = false;
this.OnPropertyChanged("Image");
}
catch
{
OnAsyncFail();
}
}
Whatever strategy you choose, you need to return something first and fill it later. This is a sample which has been tested in a vanilla WinRT page; you can duplicate it by dropping it onto a page with an Image named image and a TextBlock named status. This can go in OnNavigatedTo, or another appropriate place.
BitmapImage imgsrc = new BitmapImage();
Task.Run(async () =>
{
await Task.Delay(10000);
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
new Windows.UI.Core.DispatchedHandler(() =>
{
imgsrc.UriSource = new Uri("http://www.gratuit-en-ligne.com/telecharger-gratuit-en-ligne/telecharger-image-wallpaper-gratuit/image-wallpaper-animaux/img/images/image-wallpaper-animaux-autruche.jpg");
}));
});
image.Source = imgsrc;
status.Text = "Done";
The thing I am "returning" is a BitmapImage, which is a subclass of ImageSource, so it's close to what you will want to use. The Task.Delay() is just to introduce obvious lag between startup and image population, so that you can see that the status text is set well before the image is loaded.
To make this work with your sample, you will need to create (or access) an ImageSource in your property and return it immediately, without waiting for it to be filled. Then you start off a background task that assigns the actual source data.
The actual chain of causation could be different than what I show here. You could, for instance, access the ImageSources from a pre-created collection of them. This would allow you to start loading the images before the property is ever called. But it sounds like this will be a start to get you moving in the right direction.
Related
I am trying to make simple control to preview Images in the directory.
Layout is simple as below.
The thumbnails below is a scrollViewer. With left and right arrow keyDown the thumbnail gets Focus. Which fires an event and the Image is loaded in the Image control Above, with the follwing code.
var cancellationToken = getImageCTS.Token;
var Image = await imagesList[Index].GetImageSourceAsync(cancellationToken);
ImageInView.Source = Image;
Upon losing focus, the Task is cancelled with getImageCTS.Cancel() Which I don't know if it works.
imageeList is a ObservableCollection of type ImageViewerItem, with Following Code.
[ObservableObject]
internal partial class ImageViewerItem
{
private string path;
[ObservableProperty]
private BitmapImage bitmapImg = new();
private string name;
public ImageViewerItem(string name, string path)
{
this.path = path;
this.name = name;
GetThumbSourceAsync();
}
internal async Task<BitmapImage> GetImageSourceAsync(CancellationToken token)
{
try
{
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
BitmapImage bitmapImage = new();
var file = await StorageFile.GetFileFromPathAsync(path);
using IRandomAccessStream fileStream = await file.OpenReadAsync();
token.ThrowIfCancellationRequested(); ---> Sometimes it does get cancelled at this stage.
await bitmapImage.SetSourceAsync(fileStream);
return bitmapImage;
}
catch (OperationCanceledException)
{
return null;
}
}
}
Everything works works well so far.
Problem occurs if I hold down left/right key to scroll. The GetImageSourceAsync() does not get cancelled and Image is still loaded, which heavily impacts the scrolling.
If the item quickly loses focus, there is no need to load the Image in View. The task should be cancelled, But it runs to completion and scrolling starts stuttering.
I hope I am able to explain the problem here.
I'm trying to load a BitmapImage in a background thread and then set the (WPF) image source to this BitmapImage.
I'm currently trying something like this:
public void LoadPicture()
{
Uri filePath = new Uri(Directory.GetCurrentDirectory() + "/" + picture.PictureCacheLocation);
if (Visible && !loaded)
{
if (File.Exists(filePath.AbsolutePath) && picture.DownloadComplete)
{
BitmapImage bitmapImage = LoadImage(filePath.AbsolutePath);
image.Dispatcher.Invoke(new Action<BitmapImage>((btm) => image.Source = btm), bitmapImage);
loaded = true;
}
}
}
But I get an InvalidOperationException because the background thread owns the BitmapImage.
Is there a way to give the ownership of the BitmapImage or a copy to the UI Thread?
I need to load the bitmap image in the background thread because it may block for a long time.
All work with the DependencyObject should happen in one thread.
Except for frozen instances of FrŠµezable.
It also makes no sense (in this case) to pass a parameter to Invoke - it is better to use a lambda.
There is also the danger of the Dispatcher self-locking, since you are not checking the flow.
public void LoadPicture()
{
Uri filePath = new Uri(Directory.GetCurrentDirectory() + "/" + picture.PictureCacheLocation);
if (Visible && !loaded)
{
if (File.Exists(filePath.AbsolutePath) && picture.DownloadComplete)
{
BitmapImage bitmapImage = LoadImage(filePath.AbsolutePath);
bitmapImage.Freeze();
if (image.Dispatcher.CheckAccess())
image.Source = bitmapImage;
else
image.Dispatcher.Invoke(new Action(() => image.Source = bitmapImage));
loaded = true;
}
}
}
Objects of type FrŠµezable do not always allow themselves to be frozen.
But your code is not enough to identify possible problems.
If you fail to freeze, then show how the LoadImage (Uri) method is implemented.
I have a image processing application in which I have 2 modes. Manual inspection and Automatic inspection using a PLC command.
Manual inspection works fine, the user clicks a button to grab an image, and then clicks another button to process the image and send results to the PLC.
But in automatic inspection mode, im getting incorrect inspection result(same product/image inspection in manual mode gets correct result). I suspect that after grabbing image the system is not getting enough time to read the full image before the inspection starts, so I added a thread.sleep(500), But that didnt make any difference. So I tried the async await.task.delay(500) method, same result.
Is there any other way to fix it?
Code:
private async void timer3_Tick(object sender, EventArgs e)
{
btncheckm1_Click(null, null);
var newSignal = textBox8.Text.Contains("+1");
var isRisingEdge = newSignal && (_oldSignal == false);
_oldSignal = newSignal;
if (isRisingEdge)
{
lblmessages.Text = "";
totalsheetcount++;
btngrabimage_Click(null, null);
// Thread.Sleep(300);
await Task.Delay(1000);
processimage();
}
}
btngrabimage() has the following code:
try
{
camimage = null;
cam1.BeginAcquisition();
// Retrieve an image
IManagedImage rawImage = cam1.GetNextImage();
IManagedImage convertedImage = rawImage.Convert(PixelFormatEnums.Mono8);
imagetoinspect = convertedImage.bitmap;
rawImage.Release();
cam1.EndAcquisition();
//cam1.DeInit();
distort();
}
catch (Exception ee)
{
}
I also would say that wrapping the grabbing of an image inside a task and awaiting that result is the way to go. The image class represents your return type as the image. Dont mind the function names though. Task.Run and Task.Factory.StartNew are essentially the same thing, just wanted to show you have different options for creating your task
public class ImageRetriever
{
public void ProcessImage()
{
//if you would like to get rid of the async
//reminder that calling .Result is blocking for the current thread.
//this means the thread will stop working untill the result is returned
//could be an issue if this is called on UI thead
var imageTask = GetImage();
Image image = imageTask.Result;
}
public async Task ProcessImageAsync()
{
//if you want to keep the async-nonblocking
Image image = await GetImage();
}
private Task<Image> GetImage()
{
//this is how your create your task
Task<Image> imageTask;
imageTask = Task.Run(() =>
{
return new Image();
});
// or
imageTask = Task.Factory.StartNew(() =>
{
return new Image();
});
return imageTask;
}
}
public class Image
{
}
I'm trying to access a database, get an image and return it so it then can be shown in a Border object. (So I'm converting the byte[] data to an image, too)
This process takes enough time to notice that the UI has frozen.
Here's some code:
ImageBrush imgb = new ImageBrush();
imgb.ImageSource = GlobalDB.GetImage(album.name, 400); // Size is 400px, this is the time-consuming part.
AlbumArt.Background = imgb;
I tried using a backgroundworker but that gave me an exception saying I can't call from different threads. I got that exception since the BackgroundWorker.RunWorkerCompleted apparently was owned by a different thread(?)
Adding to that last bit: the Runworkercompleted did this:
AlbumArt.Background = imgb;
I now don't know what to do.
Any suggestions will be appreciated!
RunWorkerCompleted gets called on UI thread only.
Only issue i can see in your code is imgb is created in background thread and WPF has constraint that DependencyProperty source and Dependency object should be created on same thread.
So before assigning imgb to Background call Freeze() on it:
imgb.Freeze();
AlbumArt.Background = imgb;
Reason being freezed objects are allowed to be access across threads. They are not thread affine.
I have to ask. Where is your view model? Rohit Vats's answer will more or less get the job done, but you are not approaching this idiomatically. You should have a ViewModel with code something like this:
public class AlbumViewModel: BaseViewModel // BaseViewModel is your code that implements INotifyPropertyChanged
{
private string name
public string Name
{
get{ return name;}
set
{
if(name != value)
{
name = value;
FirePropertyChanged("Name");
LoadBackgroundImageAsync(value);
}
}
}
private ImageSource backgroundImage;
public ImageSource BackgroundImage
{
get{return backgroundImage;}
private set
{
if(backgroundImage != value)
{
background = value;
FirePropertyChanged("BackgroundImage");
}
}
}
private Task LoadBackgroundImageAsync(string name)
{
var retv = new Task(()=>
{
var source = GlobalDB.GetImage(name, 400);
source.Freeze();
BackgroundImage = source;
});
retv.Start();
return retv;
}
}
Then just bind to the property and let WPF wory about updating the UI.
not sure about what GetImage are doing, but maybe you can enclosure your code into a Task:
Task.Factory.StartNew(() => {
ImageBrush imgb = new ImageBrush();
imgb.ImageSource = GlobalDB.GetImage(album.name, 400);
AlbumArt.Background = imgb;
});
As already suggested in another answer, you may alternatively use an asynchronous task. But you have to dispatch the assignment of the Background property to the UI thread after freezing the ImageBrush.
Task.Factory.StartNew(() =>
{
var imgb = new ImageBrush(GlobalDB.GetImage(album.name, 400));
imgb.Freeze();
Dispatcher.BeginInvoke(new Action(() => AlbumArt.Background = imgb));
});
I had some performance issues when using an UICollectionView where the cell was showing an Image (loaded from the HD).
I solved that issue by loading the image in the background.
Basically, in my "GetCell" method I check if the image is in my ImageCache.
If so, set the image on my ImageView in the cell.
If not, load the image in the background and request a reload for that specific
item (I request a reload because I don't know if the cell is recycled
in the meanwhile, so it's not safe to directly set the image)
Snippet of my background process:
ThreadPool.QueueUserWorkItem (delegate {
ImagesCache.AddImageToCache(""+indexPath.Row,ImageForPosition(indexPath.Row));
InvokeOnMainThread (delegate {
collectionView.ReloadItems(Converter.ToIndexPathArray(indexPath));
});
});
It works ok, but if you scroll fast, it will load all these async tasks and the problem is that it will execute the requests in order (FIFO). So when you scroll fast, images for invisible cells will be loaded before images from visible cells.
Does anyone has an idea how I could optimise this process to get a better user experience?
Because if I would extend this to include images from the internet, the problem will be worse (because of the download).
Increasing the max amount of simultaneously threads will allow the later added threads to already start immediately but it will decrease the overall performance/download speed, so that's also not a real solution.
Thanks,
Matt
My project's solution in short: one thread to download images with queue support. Plus checking in target UI control, that it wasn't dequeued for reuse.
Long version:
Implement queue with methods Start/Stop. When Start is calling, start background thread, which in busy loop (while true { DoSomething(); }) will try to dequeue request from queue. If not dequeued, sleep a bit. If dequeued, execute it (download image). Stop method should say thread to exit from loop:
public void Start()
{
if (started) {
return;
}
started = true;
new Thread (new ThreadStart (() => {
while (started) {
var request = GetRequest();
if (request != null) {
request.State = RequestState.Executing;
Download (request);
} else {
Thread.Sleep (QueueSleepTime);
}
}
})).Start ();
}
public void Stop()
{
started = false;
}
Then, make a private method in queue to download image with such logic: check for image in file cache. If file is available, read and return it. If not, download it, save it to file, return it (call Action<UIImage> onDownload) or error (call Action<Exception> onError). Call this method in queue's busy loop. Name it Download:
public Download(Request request)
{
try {
var image = GetImageFromCache(request.Url);
if (image == null) {
image = DownloadImageFromServer(request.Url); // Could be synchronous
}
request.OnDownload(image);
} catch (Exception e) {
request.OnError(e);
}
}
Then, make a public method to add request to queue. Pattern Command is useful for wrapping requests for queue: storing Actions, current State. Name it DownloadImageAsync:
public DownloadImageAsync(string imageUrl, Action<UIImage> onDownload, Action<Exception> onError)
{
var request = MakeRequestCommand(imageUrl, onDownload, onError);
queue.Enqueue(request);
}
When UITableViewCell is preparing to show and request to download image:
// Custom UITableViewCell method, which is called in `UITableViewSource`'s `GetCell`
public void PrepareToShow()
{
var imageURLClosure = imageURL;
queue.DownloadImageAsync(imageURL, (UIImage image) => {
if (imageURLClosure == imageURL) {
// Cell was not dequeued. URL from request and from cell are equals.
imageView.Image = image;
} else {
// Do nothing. Cell was dequeued, imageURL was changed.
}
}, (Exception e) => {
// Set default image. Log.
});
}
Checking for (imageURLClosure == imageURL) is imprtant to avoid showing multiple images in one UIImageView when scrolls fast. One cell could initialize multiple request, but only last one result should be used.
Futher improvements:
LIFO execution. If no requests are already run, add new one to begin.
Using Action<byte[]> onDownload instead of Action<UIImage> onDownload for cross-platform code compatibility;
Availability to cancel download image request when cell become insivisble (WillMoveToSuperview). Well, it's not very necessary. After first downloading, image will be in cache, so any further requests for image will be done fast. Thanks to cache;
In-memory cache. Thus, in worst case, chain will be:
Find image in in-memory cache -> Find image in file cache -> Downloading from server.