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.
Related
I acquire an image from a camera about 30 times / second, and, during I'm converting this image, I would like to prevent the camera to free the source image.
In this code, bmp is the source (the image acquired by the camera) and writeablebitmap the destination image (after the conversion and the image displayed)
For that, I used lock
Messenger.Default.Register<Bitmap>(this, (bmp) =>
{
ImageTarget.Dispatcher.BeginInvoke((Action)(() =>
{
if (ImageTarget.Source == null && bmp != null)
{
ImageTarget.Source = writeableBitmap;
}
Object tempLock = new Object();
lock (tempLock)
{
Util.ImageConverter.Convert(bmp, writeableBitmap);
}
}));
});
this method seems working, but, it's really have bad performance (cause the new 30times/second). I see on https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx that I could use the same instance of tempLock.
So my code part becomes :
Object tempLock = new Object();
Messenger.Default.Register<Bitmap>(this, (bmp) =>
{
ImageTarget.Dispatcher.BeginInvoke((Action)(() =>
{
if (ImageTarget.Source == null && bmp != null)
{
ImageTarget.Source = writeableBitmap;
}
lock (tempLock)
{
Util.ImageConverter.Convert(bmp, writeableBitmap);
}
}));
});
The problem is that the writeableBitmap is always black.
Why I'm doing bad ?
Thanks
Edit: I found a solution, I put the Object tempLock = new Object(); outside the constructor (directly in the class and it's work, but I don't know why)
When you put the Object tempLock = new Object(); outside the constructor, you are initializing it only once in the main thread. However when you put it inside the ImageTarget.Dispatcher.BeginInvoke() you are initializing it on a background thread. So the value of tempLock remains null on the main thread and it is not possible to lock on a null value.
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'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.
I'm trying to get a lot of images from a webserver, so not to overload the server with hundreds of requests per second I only let a few through which is handled in WebService. The following code is on the object that saves the image, and where all the binding goes to.
ThreadStart thread = delegate()
{
BitmapImage image = WebService.LoadImage(data);
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
{
this.Image = image;
}));
};
new Thread(thread).Start();
The image is loaded just fine, the UI works fluidly while the image loads, however this.Image = image is never called. If I use Dispatcher.CurrentDispatcher.Invoke(..) the line is called, but doesn't work for setting the Image.
Why doesn't the dispatcher invoke my action?
Since you create you BitmapImage on a worker thread, it's not owned by the WPF thread.
Maybe this code can help you to solve the issue:
The code you posted
ThreadStart thread = delegate()
{
BitmapImage image = WebService.LoadImage(data, Dispatcher.CurrentDispatcher);
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
{
this.Image = image;
}));
};
new Thread(thread).Start();
How you could change your WebService.LoadImage to "make it work"
BitmapImage LoadImage(object data, Dispatcher dispatcher)
{
// get the raw data from a webservice etc.
byte[] imageData = GetFromWebserviceSomehow(data);
BitmapImage image;
// create the BitmapImage on the WPF thread (important!)
dispatcher.Invoke(new Action(()=>
{
// this overload does not exist, I just want to show that
// you have to create the BitmapImage on the corresponding thread
image = new BitmapImage(imageData);
}));
return image;
}
System.Object
|-> System.Windows.Threading.DispatcherObject
|-> System.Windows.DependencyObject
|-> System.Windows.Freezable
|-> ...
|-> System.Windows.Media.Imaging.BitmapImage
BitmapImage is thread affined. So this control and BitmapImage object should be created in the same thread.
Also you can try to just Freeze image, but it seems it will not help.
BeginInvoke does not reveal error because it is handled by WPF. See MSDN how to set up WPF tracing.
WPF is single threaded. Read some book about WPF where all staff described.
In my program there is a BackgroundWorker class that preloads the images to BitmapImage object. I need to pass that preloaded image to the main application(WPF), where it will be copied to another BitmapImage object. This seems to be working, however, when i try
imgViewer.Source = imgNext; //imgNext is a main app copy of the preloaded image
an error occurs meaning that that object(imgNext) is owned by another thread and it cannot be used.
Any ideas how to get rid of it and get the code working?
Thanks everybody for answering!
In fact, I managed to solve this problem by creating a static BitmapImage inside App class.
Before using it, I do
App.iNext = null;
Then I load the actual image and freeze it, so this static property can be accessed from everywhere. When the cycle repeats many times, assigning null prevents 'object is frozen' errors.
And of course, there was a lot of work with managing single BGW instance, queuing tasks etc.
(Currently I'm using ImagesContainer class defined also in my program that has two BitmapImage properties. I use it to receive preloaded images from backgroundworker. )
imgNext is a public variable defined in MainWindow. (main thread)
void bwImgLoader_DoWork(object sender, DoWorkEventArgs e)
{
backgrLoadNextPrevList list = e.Argument as backgrLoadNextPrevList;
ImagesContainer result = new ImagesContainer();
if (list.HasNextPath) result.imgPrev = PrepareImage(list.NextPath);
if (list.HasPrevPath) result.imgNext = PrepareImage(list.PrevPath);
e.Result = result;
}
void bwImgLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ImagesContainer result = e.Result as ImagesContainer;
if (result.imgNext != null)
{
setNextDelegate s = new setNextDelegate(setNext);
object[] t = { result.imgNext };
imgNext.Dispatcher.Invoke(s, t);
}
// do not take into account this line, just ignore it.
//if (result.imgPrev != null) imgPrev = result.imgPrev;
}
public void setNext(BitmapImage b)
{
imgNext = b;
}
public delegate void setNextDelegate(BitmapImage b);
Freezing the bitmapimage helps only on the first background load(see the comment under the answer below). When I call BackgroundWorker second time, an errors occures that the object is frozen and cannot be modified. Is there a way to un-freeze it?
Or, is there any way to copy data from one thread to another without copying an attribution to thread?
UPDATED
Thanks everybody for answering!
In fact, I managed to solve this problem by creating a static BitmapImage inside App class.
Before using it, I do
App.iNext = null;
Then I load the actual image and freeze it, so this static property can be accessed from everywhere. When the cycle repeats many times, assigning null prevents errors.
And of course, there was a lot of work with managing single BGW instance, queuing tasks etc.
But these efforts were worth the result - I got +125% in performance!!! Thank everyone!
BitmapImage is Freezable so you can Freeze() it after loading it. This will permit access from any thread.
It's easiest to create all UI objects on the same thread. This includes any classes descending from DispatcherObject, such as BitmapImage.
On the UI thread - before creating the BGW - capture the result of TaskScheduler.FromCurrentSynchronizationContext. You can stick it in a private member of your class. e.g.:
private TaskScheduler ui;
public void InitiateBGW()
{
this.ui = TaskScheduler.FromCurrentSynchronizationContext();
this.bwImgLoader.RunWorkerAsync();
}
On the BGW, whenever you need to access BitmapImage functionality (creating them or modifying them), queue it to the TaskScheduler like this:
private BitmapImage PrepareImage(string path)
{
// This code runs in a BGW.
// Load underlying bitmap (non-UI)...
var bitmap = ..;
// Prepare the bitmap (non-UI)...
return Task.Factory.StartNew(() =>
{
var ret = new BitmapImage();
// Load "bitmap" into "ret"
return ret;
}, CancellationToken.None, TaskCreationOptions.None, this.ui);
}