tags:

views:

112

answers:

3

Using threading in WPF makes no difference for me. The UI continues to be not responsive.

This is my code:

private void button1_Click_1(object sender, RoutedEventArgs e)
{
    Thread th = new Thread(new ThreadStart(LoadImages));
    th.Start();
}

void LoadImages()
{
    this.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate() {
        IService1 svc = ConnectAndGetObject();
        foreach (byte[] imgbytes in svc.GetImageDateWise(datePicker1.DisplayDate, DateTime.Now, "test"))
        {
            using (MemoryStream mem = new MemoryStream(imgbytes))
            {
                BitmapImage jpgimage = new BitmapImage();
                jpgimage.BeginInit();

                jpgimage.CacheOption = BitmapCacheOption.OnLoad;

                jpgimage.StreamSource = mem;

                jpgimage.EndInit();

                //  PngBitmapDecoder decodejpg = new PngBitmapDecoder(mem, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnDemand);
                Image wpfimage = new Image();
                wpfimage.Source = jpgimage.Clone();

                lbx.Items.Add(wpfimage);
                lbx.UpdateLayout();
                Thread.Sleep(1000);
            }
        }
    }));
}

Updated working code:

      List<MemoryStream> mems = new List<MemoryStream>();
        void LoadImages()
        {

            IService1 svc = ConnectAndGetObject();
            foreach (byte[] imgbytes in svc.GetImageDateWise(GetDate(), DateTime.Now, "test"))
            {
   this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() {
       mems.Add(new MemoryStream(imgbytes));


                    BitmapImage jpgimage = new BitmapImage();
                    jpgimage.BeginInit();

                    jpgimage.CacheOption = BitmapCacheOption.None;

                    jpgimage.StreamSource = mems[mems.Count-1];

                    jpgimage.EndInit();

                  Image wpfimage = new Image();
                  wpfimage.Source = jpgimage.Clone();
                    lbx.Items.Add(wpfimage);
                    lbx.UpdateLayout();
                    Thread.Sleep(500);


   }));
            }
            mems.Clear();


        }
+1  A: 

You're doing too much in your delegate. This might be the cause of your problems, plus the 1 second sleep you have in there.

Just have the code that updates the list box in the delegate:

        lbx.Items.Add(wpfimage);
        lbx.UpdateLayout();

Failing that raise an event in your thread and subscribe to it in your main application and update the UI from that.

You'll need a method along these lines:

    private void ImageAdded_EventHandler(object sender, ImageAddedEventArgs e)
    {
        Action action = () => ImageAdded(e.Image);
        if (Dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            Dispatcher.Invoke(DispatcherPriority.Normal, action);
        }
    }

Where ImageAddedEventArgs is a class based on EventArgs that has a property of the image data.

ChrisF
or use the WPF `Dispatcher`: http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
0xA3
@divo - see my update
ChrisF
I tried removing the Thread.sleep and the lbx.updatelayout stuff.....Now,the listbox doesnt get updated immediately...but the Application still continues to get stuck for a few seconds when I click the button until the process is over.
Josh
@Josh - see Henk Holterman's answer. I missed that your entire method was inside a delegate.
ChrisF
yeah,checked that...but please see my comment there..I'm getting a new exception if i do that.
Josh
+4  A: 

In your code the entire body of LoadImages is Invoked right back to the main thread. So you do not have a multi-threaded solution at all, just a complicated one.

Here is my stab at it:

// untested
void LoadImages()
{
       // this.Dispatcher.Invoke(DispatcherPriority.Normal, 
       //    new System.Windows.Forms.MethodInvoker(delegate() {
    IService1 svc = ConnectAndGetObject();
    foreach (byte[] imgbytes in svc.GetImageDateWise(datePicker1.DisplayDate, DateTime.Now, "test"))
    {   
        using (MemoryStream mem = new MemoryStream(imgbytes))
        {    
            BitmapImage jpgimage = new BitmapImage();
            jpgimage.BeginInit();    
            jpgimage.CacheOption = BitmapCacheOption.OnLoad;    
            jpgimage.StreamSource = mem;    
            jpgimage.EndInit();

            // only invoke the part actually touching the UI    
            this.Dispatcher.Invoke(DispatcherPriority.Normal, 
              new System.Windows.Forms.MethodInvoker(delegate() {
                  Image wpfimage = new Image();
                  wpfimage.Source = jpgimage; //.Clone();
                  lbx.Items.Add(wpfimage);
                  lbx.UpdateLayout();   } ));

            Thread.Sleep(1000);
        }
    }    
   //  }));
}
Henk Holterman
If I use the code you have provided,I'm getting this exception "The calling thread must be STA, because many UI components require this." on the line "Image wpfimage = new Image();"
Josh
You can move those 2 lines inside the Invoke part. And I don't think you need the Clone() call.
Henk Holterman
If I move it in,it throws this at me "Exception has been thrown by the target of an invocation."...It does not point to any line on the IDE...IDE shows a message "No Symbols are loaded for any call stack frame.The source code cannot be displayed"
Josh
I just edited, does your code looks like this?
Henk Holterman
It might be the BitmapCacheOption, I'll look it up.
Henk Holterman
tried changing the BitmapCacheOption to none...still getting the same error. My code looks exactly like that...here's the same pasted from my VS IDE... this.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate() { Image wpfimage = new Image(); wpfimage.Source = jpgimage.Clone(); lbx.Items.Add(wpfimage); lbx.UpdateLayout(); }));
Josh
OK, note that I put the BeginInit/EndInit back, my mistake.
Henk Holterman
no Henk,actually I had to start the dispatcher the very next line after the loop and end it when the loop ends. Apart from that,it expected me not to dispose the memory stream for some reason...and now it works after I changed that.I really have no idea why the error actually came though
Josh
But Thank You Henk...You tried a great deal to help me...I've posted the code that actually worked for me.
Josh
+2  A: 

The problem is that the first thing your non UI thread does is invoke a delegate on the UI thread. The net result is a lot of overhead with no cocurrency at all.

Reorg your code so that only UI tasks are done on the UI thread.

void LoadImages() 
{ 
    IService1 svc = ConnectAndGetObject(); 
...
Dispatcher.Invoke( () => lbx.Items.Add(wpfimage));
....
} 
Scott Weinstein