Dispatcher.BeginInvoke(...) not dispatching anything - c#

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.

Related

How can I give an argument to Dispatcher.Invoke?

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.

Use multi-threading save UserControls as image the error is :The calling thread cannot access this object because a different thread owns it

I want use Mutli-Thread save all UserControl as image at the same time want to show animation view on the screen, but have an error:
The calling thread cannot access this object because a different
thread owns it.
This is my code:
private void OnScreenShotAllCommandExecuted(object parameter)
{
string filePath = #"D:\ScreenShot\";
IList<UserControl> userControls = LayoutRoot.Children.OfType<UserControl>().ToList();
List<Thread> tasks = new List<Thread>();
AnimationView animationView = new AnimationView();
animationView.Show();
foreach (UserControl userControl in userControls)
{
string viewName = userControl.Name;
string fileName = userControl.Name + ".png";
if (viewName != null)
{
Thread thread = new Thread(() =>
{
Thread.Sleep(1000);
SaveView(userControl, fileName, filePath);
ThreadHasFinished();
});
tasks.Add(thread);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
}
}
private void ThreadHasFinished()
{
Interlocked.Increment(ref finishedControls);
Dispatcher.BeginInvoke((Action)(() =>
{
animationView.Close();
}));
}
public static void SaveView(UserControl userControl, string fileName, string destFolder)
{
Rect bounds = VisualTreeHelper.GetDescendantBounds(userControl);
int width = (int)view.RenderSize.Width;
int height = (int)view.RenderSize.Height;
RenderTargetBitmap rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext ctx = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(userControl);
ctx.DrawRectangle(vb, new Pen(Brushes.Blue, 2), new Rect(new Point(0, 100), size));
}
rtb.Render(dv);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
using (Stream fileStream = new FileStream(string.Format("{0}\\{1}.png", destFolder, fileName), FileMode.Create))
{
png.Save(fileStream);
}
}
The error
The calling thread cannot access this object because a different
thread owns it.
is on this line
Rect bounds = VisualTreeHelper.GetDescendantBounds(userControl);
Thread thread = new Thread(() =>
{
Thread.Sleep(1000);
SaveView(userControl, fileName, filePath);
ThreadHasFinished();
});
In the above code you are calling SaveView(userControl, fileName, filePath) which is wrong as WPF update UI on the main thread.
For Responsive UI, instead of creating threads directly I suggest you to use BackgroundWorker in this case. You will have RunWorkerCompleted event which will be on the main thread, so you can update any UIElement in that. And expensive stuff you can run under DoWork event. DoWork run on the background thread from the Thread Pool.
A general design principle of many UI frameworks, including WPF and Windows Forms, is that of thread affinity. Specifically, the thread that creates a UI element is the only thread that can interact with it.
If you are trying to directly interact with your UI elements with multiple different threads, your design is fundamentally flawed.
Note that anything which does not directly interact with a usercontrol may run on a different thread. Disk I/O, etc, e.g.
To marshal between your worker thread and UI thread (99% of basic applications have exactly, only one UI thread), use the element's dispatcher from the worker thread. The Dispatcher will allow you to queue a request to the ui thread and get its result. Read up on MSDN for more detail.

Error: The calling thread cannot access this object because a different thread owns it

I get this error. Here is the code:
Image image;
BitmapImage BmpImg;
MemoryStream ms;
public void Convert()
{
ms = new MemoryStream();
image.Save(ms, ImageFormat.Jpeg);
BmpImg = new BitmapImage();
BmpImg.BeginInit();
BmpImg.StreamSource = new MemoryStream(ms.ToArray());
BmpImg.EndInit();
}
private void Btn_Click(object sender, RoutedEventArgs e)
{
Dispatcher.Invoke(new Action(() => { Image.Source = BmpImg; }));
}
How to convert a System.Drawing.Image to BitmapImage and display the same on wpf?
BmpImg is created on background thread. You cannot bind to Image Source DP object which is created on thread other than UI thread.
Since you are using Dispatcher, i assume you now how to delegate stuff on UI thread.
So all you need to do is put the creation of BmpImg on UI thread as well via Dispatcher.
You can get UI dispatcher like this as well - App.Current.Dispatcher.
OR
As #Clemens suggested in comments, if you call Freeze() on BitmapImage instance, you can access it across threads.
BmpImg.Freeze()

WPF and backgroundworker problem

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);
}

How do you pass a BitmapImage from a background thread to the UI thread in WPF?

I have a background thread that generates a series of BitmapImage objects. Each time the background thread finishes generating a bitmap, I would like to show this bitmap to the user. The problem is figuring out how to pass the BitmapImage from the background thread to the UI thread.
This is an MVVM project, so my view has an Image element:
<Image Source="{Binding GeneratedImage}" />
My view-model has a property GeneratedImage:
private BitmapImage _generatedImage;
public BitmapImage GeneratedImage
{
get { return _generatedImage; }
set
{
if (value == _generatedImage) return;
_generatedImage= value;
RaisePropertyChanged("GeneratedImage");
}
}
My view-model also has the code that creates the background thread:
public void InitiateGenerateImages(List<Coordinate> coordinates)
{
ThreadStart generatorThreadStarter = delegate { GenerateImages(coordinates); };
var generatorThread = new Thread(generatorThreadStarter);
generatorThread.ApartmentState = ApartmentState.STA;
generatorThread.IsBackground = true;
generatorThread.Start();
}
private void GenerateImages(List<Coordinate> coordinates)
{
foreach (var coordinate in coordinates)
{
var backgroundThreadImage = GenerateImage(coordinate);
// I'm stuck here...how do I pass this to the UI thread?
}
}
I'd like to somehow pass backgroundThreadImage to the UI thread, where it will become uiThreadImage, then set GeneratedImage = uiThreadImage so the view can update. I've looked at some examples dealing with the WPF Dispatcher, but I can't seem to come up with an example that addresses this issue. Please advise.
The following uses the dispatcher to execute an Action delegate on the UI thread. This uses a synchronous model, the alternate Dispatcher.BeginInvoke will execute the delegate asynchronously.
var backgroundThreadImage = GenerateImage(coordinate);
GeneratedImage.Dispatcher.Invoke(
DispatcherPriority.Normal,
new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
UPDATE
As discussed in the comments, the above alone will not work as the BitmapImage is not being created on the UI thread. If you have no intention of modifying the image once you have created it you can freeze it using Freezable.Freeze and then assign to GeneratedImage in the dispatcher delegate (the BitmapImage becomes read-only and thus threadsafe as a result of the Freeze). The other option would be to load the image into a MemoryStream on the background thread and then create the BitmapImage on the UI thread in the dispatcher delegate with that stream and the StreamSource property of BitmapImage.
You need to do two things:
Freeze your BitmapImage so it can be moved to the UI thread, then
Use the Dispatcher to transition to the UI thread to set the GeneratedImage
You'll need to access the UI thread's Dispatcher from the generator thread. The most flexible way to do this is to capturing the the value Dispatcher.CurrentDispatcher in the main thread and passing it into the generator thread:
public void InitiateGenerateImages(List<Coordinate> coordinates)
{
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
...
If you know you will only use this within a running Application and that the application will only have one UI thread, you can just call Application.Current.Dispatcher to get the current dispatcher. The disadvantages are:
You loose the ability to use your view model independently of a constructed Application object.
You can only have one UI thread in your application.
In the generator thread, add a call to Freeze after the image is generated, then use the Dispatcher to transition to the UI thread to set the image:
var backgroundThreadImage = GenerateImage(coordinate);
backgroundThreadImage.Freeze();
dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
GeneratedImage = backgroundThreadImage;
}));
Clarification
In the above code it is critical that Dispatcher.CurrentDispatcher be accessed from the UI thread, not from the generator thread. Every thread has its own Dispatcher. If you call Dispatcher.CurrentDispatcher from the generator thread you will get its Dispatcher instead of the one you want.
In other words, you must do this:
var dispatcher = Dispatcher.CurrentDispatcher;
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, dispatcher));
and not this:
var generatorThreadStarter = new ThreadStart(() =>
GenerateImages(coordinates, Dispatcher.CurrentDispatcher));
In the background thread work with Streams.
For example, in the background thread:
var artUri = new Uri("MyProject;component/../Images/artwork.placeholder.png", UriKind.Relative);
StreamResourceInfo albumArtPlaceholder = Application.GetResourceStream(artUri);
var _defaultArtPlaceholderStream = albumArtPlaceholder.Stream;
SendStreamToDispatcher(_defaultArtPlaceholderStream);
In the UI thread:
void SendStreamToDispatcher(Stream imgStream)
{
dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
var imageToDisplay = new BitmapImage();
imageToDisplay.SetSource(imgStream);
//Use you bitmap image obtained from a background thread as you wish!
}));
}

Categories