Loading a web image asynchronously - c#

I've encountered a problem with displaying an image from the web in my WPF app:
I used a BitmapImage for this task. My first attempt was to execute it in the UI thread but I quickly understood that's a no-no since the application became unresponsive until the image was completely loaded. My second attempt was to use a BackgroudWorker:
var worker = new BackgroundWorker();
worker.DoWork += worker_LoadImage;
worker.RunWorkerCompleted+=worker_RunWorkerCompleted;
worker.RunWorkerAsync(someURI);
and the worker functions:
private void worker_LoadImage(object sender, DoWorkEventArgs e)
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = e.Argument as Uri;
image.DownloadFailed += new EventHandler<ExceptionEventArgs>(image_DownloadFailed);
image.EndInit();
e.Result = image;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//if I understand correctly, this code runs in the UI thread so the
//access to the component image1 is valid.
image1.Source = e.Result as BitmapImage;
}
after that, I still got an InvalidOperationException: "The calling thread cannot access this object because a different thread owns it."
I've researched a bit and found out that since BitmapImage is Freezable, I have to call Freeze before accessing the object from another thread.
So I've tried to replace the last row in worker_LoadImage with:
image.Freeze();
e.Result = image;
But then I got an exception that my image cannot be frozen, I found out that it's probably
because the image wasn't done being downloaded when I tried to invoke Freeze(). So I added the following code to the image creation:
image.DownloadCompleted += image_DownloadCompleted;
where:
void image_DownloadCompleted(object sender, EventArgs e)
{
BitmapImage img = (BitmapImage)sender;
img.Freeze();
}
So now we get to the real question:
How do I make the background worker to wait until the image is completely downloaded and the event is fired?
I've tried many things: looping while the image's isDownloading is true, Thread.Sleep, Thread.Yield, Semaphores, Event wait handles and more.
I dont know how the image downloading actually works behind the scenes but what happens when I try one of the methods above is that the image never finishes to download (isDownloading is stuck on True)
Is there a better, simpler way to achieve the rather simple task im trying to accomplish?
Some things to notice:
this answer actually works, but only once: when I try to load another image it says the dispatcher is closed. Even after reading a bit about Dispatchers, I don't really understand how the OP achieved that or if it's possible to extend the solution for more than one image.
When I put a message box before the worker exits his DoWork function, I click OK and the image apears which means the download continued while the message box was opened and finished before I clicked OK.

Given that you're using the bitmap's ability to asynchronously load the image you don't need a BackgroundWorker in the first place. Rather than creating a BGW to start an asynchronous operation and wait for it to finish, just use the asynchronous operation directly.
All you have to do is update the UI from your image_DownloadCompleted handler (after freezing the image) and you no longer need a BGW anymore:
private void FetchImage(Uri uri)
{
var context = SynchronizationContext.Current;
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = uri;
image.DownloadFailed += image_DownloadFailed;
image.DownloadCompleted += (s, args) =>
{
image.Freeze();
context.Post(_ => image1.Source = image, null);
};
image.EndInit();
}

Related

Updating Image control in UI thread from a timer callback in WPF

I have an image control in a Xaml file as follows:
<Viewbox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Center">
<Image Name="Content"/>
</Viewbox>
I'd like to update this image with a different image every 10 seconds.
I create a system.threading.Timer instance, initialize it with a callback, pass in the UI control as a state object and set the interval to 10 seconds as follows:
contentTimer = new Timer(OnContentTimerElapsed, Content , 0 , (long) CONTENT_DISPLAY_TIME);
The callback looks as follows:
private void OnContentTimerElapsed( object sender )
{
Image content = (Image)sender;
//update the next content to be displayed
nCurrentImage = (nCurrentImage % NUM_IMAGES) + 1;
//get the url of the image file, and create bitmap from the jpeg
var path = System.IO.Path.Combine(Environment.CurrentDirectory, "../../DisplayContent/Image_" + nCurrentImage.ToString() + ".jpg");
Uri ContentURI = new Uri(path);
var bitmap = new BitmapImage(ContentURI);
//update the image control, by launching this code in the UI thread
content.Dispatcher.BeginInvoke(new Action(() => { content.Source = bitmap; }));
}
I still keep getting the following exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object because a different thread owns it.
I was able to get a solution by updating just the numCurrentImage variable, and then updating the Content.Source in the MainWindow class in callbacks running on the UI thread, something as follows (note, I'm getting frames at 30fps from a kinect):
int nCurrentImage;
Public MainWindow()
{
InitializeComponent();
nCurrentImage = 1;
System.Timers.Timer contentTimer = new System.Timers.Timer(OnContentTimerElapsed, CONTENT_DISPLAY_TIME);
contentTimer.Elapsed += OnContentTimerElapsed;
...
//Some kinect related initializations
...
kinect.multiSourceReader.MultiSourceFrameArrived += OnMultiSourceFrameArrived;
}
private void OnContentTimerElapsed( object sender )
{
//update the next content to be displayed
nCurrentImage = (nCurrentImage % NUM_IMAGES) + 1;
}
private void OnMultiSourceFrameArrived(object sender, MultiSourceFrameArrivedEventArgs e)
{
UpdateContent(nCurrentImage);
}
private void UpdateContent(int numImage)
{
var path = System.IO.Path.Combine(Environment.CurrentDirectory, "../../DisplayContent/Image_" + numImage.ToString() + ".jpg");
Uri ContentURI = new Uri(path);
var bitmap = new BitmapImage(ContentURI);
Content.Source = bitmap;
}
Even though that works, it just doesn't make good programming sense to update it that way, since half of the work is being done by one thread, and the rest by the UI thread.
Any Ideas what I'm doing wrong?
Even though that works, it just doesn't make good programming sense to update it that way, since half of the work is being done by one thread, and the rest by the UI thread.
Actually this is exactly what you want to be doing. You want to be doing your non-UI work in a non-UI thread, and doing your UI work in a UI thread.
That said, while this is fundamentally what you want to be doing, you don't need to do all of this yourself so explicitly. You can simply use a DispatcherTimerand it will fire the callback in the UI thread, rather than a thread pool thread. It is, more or less, doing much of what you're doing manually.
Update the XAML image element, I like to name them all with an X to remind me it's a XAML element.
<Image Name="XContent"/>
When timer fires,
...
bitmap.Freeze();
XContent.Dispatcher.Invoke(()=>{
XContent.Source = bitmap;
}

New thread never completes waiting on WebBrowser

In creating jpg images, this code uses threading. However, the Thread.Join() sometimes hangs on creating particular images. I have researched, and it seems as if I should be using BeginInvoke() instead. How could I rewrite the following code from using Thread.Join() to BeginInvoke()?
public Bitmap Generate()
{
var m_thread = new Thread(_Generate);
m_thread.SetApartmentState(ApartmentState.STA);
m_thread.Start();
m_thread.Join();
return m_Bitmap;
}
private void _Generate()
{
var browser = new WebBrowser {ScrollBarsEnabled = false };
browser.ScriptErrorsSuppressed = true;
browser.Navigate(m_Url);
browser.DocumentCompleted += WebBrowser_DocumentCompleted;
while (browser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
browser.Dispose();
}
Looking at your code I see one issue. You've registered to the DocumentCompleted event after the Navigate() call. So theoretically it's possible that the event have been fired before you've registered your handler.
Try to swap the two lines and see whether you get your problem fixed.
I believe that'll be the case if the image has already been retrieved and was cached.

Getting exception while adding images to listview to display using WPF

I have been working on wpf to make a image slider.
I have used ListView to diplay list of images and an Image Controller to Preview it.
Everything Works fine but when I load images more than 100, I get an unhandled exception like (thread currently not...Stack etc) something like that,I don't remember it properly.
Loading more than one hundred images makes the WPF Application slower and slower along the PC.
So please help me How can I overcome the problem thanks.
you should use Thread or Dispatcher when loading large number of images. also you can use BackgroundWorker for Async load.
public LoadImages()
{
BackgroundWorker BWorker = new BackgroundWorker();
BWorker.DoWork += new DoWorkEventHandler(BWorker_DoWork);
BWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(BWorker_RunWorkerCompleted);
BWorker.RunWorkerAsync();
}
void BWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BitmapSource bitmap = e.Result as BitmapSource;
Dispatcher.BeginInvoke(DispatcherPriority.Normal
(ThreadStart)delegate()
{
Image image = new Image();
image.Source = bitmap;
background.Child = image;
});
}
void BWorker_DoWork(object sender, DoWorkEventArgs e)
{
BitmapSource bitmapSource = IMAGEFROMFILE;
e.Result = bitmapSource;
}
also you can user BitmapDecoder for height and width to reduce the size on the load time.

Rendering a program icon to the Image Control using TPL

Hi i'm trying to create an Interactivity.Behavior to load a program's icon in the background. The following is the code but it gave me The calling thread cannot access this object because a different thread owns it..
protected override void OnAttached()
{
base.OnAttached();
if (!string.IsNullOrEmpty(Url))
{
Icon ico = Icon.ExtractAssociatedIcon(Url);
if (ico != null)
{
taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => {
MemoryStream ms = new MemoryStream();
ico.ToBitmap().Save(ms, ImageFormat.Png);
ms.Position = 0;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
return bi;
}).ContinueWith((t) => AssociatedObject.Source = t.Result, taskScheduler);
}
}
}
WPF objects (anything that descends from DispatcherObject) are thread-affine -- normally they can only be used on the thread that created them. This includes BitmapImage objects. If you create the BitmapImage on a background thread, then it can only be used from that background thread -- which means that the UI thread will get an error when it tries to display the bitmap.
However, BitmapImage descends from Freezable. Freezable has a Freeze method that will make the instance read-only. And per the "Freezable Objects Overview" on MSDN:
A frozen Freezable can also be shared across threads, while an unfrozen Freezable cannot.
So if you add a call to bi.Freeze(); just before you return the image from your background task, then you should be able to use the image successfully from your UI thread.
Although you are using CurrentSynchronizationContext, give it a try if may have to be run on the Dispatcher of the icon....
ico.Dispatcher.BeginInvoke(
new Action(
() =>
{
ico.ToBitmap().Save(ms, ImageFormat.Png);
///rest of the code that uses `ms`.
}));
Suggestion: Why arent you using Priority Binding and Binding.IsAsync for slow loading images....
http://social.msdn.microsoft.com/Forums/en-AU/wpf/thread/b3dc9baa-4cf6-49ed-a316-b9fb1cd29516

TwainDotNet Scanning using TWAIN with BackgroundWorker

Has anyone tried TwainDotNet for scanning with TWAIN API calls from .NET? Though it works well usually I've some issues with it when used along with WPF application using MVVM. Basically I'm calling Twain scanning functions from a Service, which in turn uses a BackgroundWorker.
List<BitmapSource> bitmapSources = new List<BitmapSource>();
Twain twain = new Twain(new WpfWindowMessageHook(_window));
ScanSettings settings = new ScanSettings() { ShowTwainUI = false };
using (BackgroundWorker worker = new BackgroundWorker())
{
worker.DoWork += (sndr, evnt) =>
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
EventHandler scanCompleteHandler = (se, ev) => { waitHandle.Set(); };
twain.ScanningComplete += scanCompleteHandler;
twain.StartScanning(settings);
waitHandle.WaitOne();
if (twain.Images.Count > 0)
{
foreach (var image in twain.Images)
{
BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(new Bitmap(image).GetHbitmap(),
IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
bitmapSources.Add(bitmapSource);
}
}
};
worker.RunWorkerCompleted += (sndr, evnt) => { image1.Source = bitmapSources[0]; };
worker.RunWorkerAsync();
}
ScanningComplete event handler is never fired when we are working with a BackgroundWorker. Any suggestions to resolve this issue?
The fact that the Twain object requires a window handle in its object constructor suggests that something inside the Twain object requires message handling. Cross-thread message handling is tricky to begin with but even more so when it's happening inside an API.
If the twain API creates a window handle (overtly, such as a popup window or dialog, or secretly, such as for interprocess communication (IPC)) as part of one of the API functions you're calling from the background thread, that window handle will be bound to the thread it was created on - the background thread. All messages sent to that window handle will queue up waiting for the background thread to process them in a message loop. You don't have a message loop in your background thread, so that window handle will get stuck in limbo. It won't respond to window messages. Posted messages will go unanswered. SendMessage() will deadlock.
Even if this is not a window handle / message loop problem, it is very likely that if the Twain API was not explicitly and deliberately implemented with multithreading in mind, it will have problems when used across threads. You are creating the twain object in one thread and then using it in another thread, so this is a cross-thread situation. If you could create the twain object in the background thread and only use the twain object in the context of that background thread, this might work around thread affinity issues in the twain API implementation. When window handles and messages are involved, moving everything to the background thread is just as likely to make things worse.
The ability to use an object across threads does not come for free. If the twain API was not designed for use across threads, there is little you can do to make it work across threads. Your best bet is to keep the Twain object in the main UI thread.
Have you tried removing the LINQ'ness from the code and put it into a separate function to actually test this out first, note that I have it wrapped up in a try/catch block to see if there's any error, also notice that I created a simple class WorkerArgs for passing the data around as it is non-LINQ code, it would be interesting to see what results there are (if any):
public class WorkerArgs{
public List<BitMapSource> _bitmapSources;
public Twain _twain;
public ScanSettings _settings;
}
List<BitmapSource> bitmapSources = new List<BitmapSource>();
Twain twain = new Twain(new WpfWindowMessageHook(_window));
ScanSettings settings = new ScanSettings() { ShowTwainUI = false };
WorkerArgs wArgs = new WorkerArgs();
wArgs._bitmapSources = bitmapSources;
wArgs._twain = twain;
wArgs._settings = settings;
using (BackgroundWorker worker = new BackgroundWorker())
{
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync((WorkerArgs)wArgs);
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try{
image1.Source = (WorkerArgs(e.Argument))._bitmapSources[0];
}catch(Exception up){
throw up; // :P
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
try{
WorkerArgs thisArgs = (WorkerArgs)e.Argument as WorkerArgs;
if (thisArgs != null){
AutoResetEvent waitHandle = new AutoResetEvent(false);
EventHandler scanCompleteHandler = (se, ev) => { waitHandle.Set(); };
thisArgs._twain.ScanningComplete += scanCompleteHandler;
thisArgs._twain.StartScanning(settings);
waitHandle.WaitOne();
if (thisArgs._twain.Images.Count > 0)
{
foreach (var image in twain.Images)
{
BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(new Bitmap(image).GetHbitmap(),
IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
thisArgs._bitmapSources.Add(bitmapSource);
}
}
}
}catch(Exception up){
throw up; // :P
}
}
I couldn't help noticing, it's just after entering the code I noticed this:
Twain twain = new Twain(new WpfWindowMessageHook(_window))
Are you doing hooking or something like that within the background worker - perhaps there's a cross thread problem hence ScanningComplete is not being fired? Just a thought, Can you clarify anyway?

Categories

Resources