20

Why I can't create CroppedBitmap in the following code? I got an exception:

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

If I change the code to

CroppedBitmap cb = new CroppedBitmap(new WriteableBitmap(bf), new Int32Rect(1, 1, 5, 5));

the exception is gone? why ?

Code 1, an exception at cb.Freeze():

public MainWindow()
{
    InitializeComponent();

    ThreadPool.QueueUserWorkItem((o) =>
        {
            //load a large image file
            var bf = BitmapFrame.Create(
                new Uri("D:\\1172735642.jpg"),
                BitmapCreateOptions.None,
                BitmapCacheOption.None);
            bf.Freeze();
            Dispatcher.BeginInvoke(
                new Action(() =>
                    {
                        CroppedBitmap cb = new CroppedBitmap(bf, new Int32Rect(1,1,5,5));
                        cb.Freeze();
                        //set Image's source to cb....
                    }), 
                    DispatcherPriority.ApplicationIdle);
         }
    );
}

Code 2, works:

    ThreadPool.QueueUserWorkItem((o) =>
    {
        var bf = BitmapFrame.Create(
                new Uri("D:\\1172740755.jpg"),
                BitmapCreateOptions.None,
                //BitmapCreateOptions.DelayCreation,
                BitmapCacheOption.None);
        bf.Freeze();
        var wb = new WriteableBitmap(bf);
        wb.Freeze();
        this.Dispatcher.Invoke(
            new Action(() =>
            {
                var r = new Int32Rect(1, 1, 5, 5);
                CroppedBitmap cb = new CroppedBitmap(wb, r);
                cb.Freeze();
                //set Image's source to cb....
                Image.Source = cb;
            }),
            DispatcherPriority.ApplicationIdle);
    }
);

Code 3, works without WritableBitmap:

ThreadPool.QueueUserWorkItem((o) =>
    {
        var bf = BitmapFrame.Create(
                new Uri("D:\\1172735642.jpg"),
                BitmapCreateOptions.None,
                //BitmapCreateOptions.DelayCreation,
                BitmapCacheOption.None);
        bf.Freeze();
        var bf2 = BitmapFrame.Create(bf);
        bf2.Freeze();

        this.Dispatcher.Invoke(
            new Action(() =>
            {
                var r = new Int32Rect(1, 1, 5, 5);
                BitmapSource cb = new CroppedBitmap(bf2, r);
                cb.Freeze();
                //set Image's source to cb....
                Image.Source = cb;
            }),
            DispatcherPriority.ApplicationIdle);
    }
);
abatishchev
  • 98,240
  • 88
  • 296
  • 433
zunyite
  • 359
  • 2
  • 4
  • 12
  • 1
    `BitmapFrame` smells like a UI class, and it's being created on a worker thread, and then used on the UI thread... – Aviad P. Apr 28 '10 at 11:22

4 Answers4

16

Following code might help you solve the issue of updating a gui element from another thread :

Module level

delegate void updateCallback(string tekst);

This is the method to update your element :

private void UpdateElement(string tekst)
{
    if (element.Dispatcher.CheckAccess() == false)
    {
        updateCallback uCallBack = new updateCallback(UpdateElement);
        this.Dispatcher.Invoke(uCallBack, tekst);
    }
    else
    { 
//update your element here
    }
 }
Terry
  • 5,132
  • 4
  • 33
  • 66
7

When working with WPF be aware that if you create a UI object in one thread you can't access it from another thread. Your UI objects should (typically) be created the UI thread, and then you need the UI thread to access them later. No other thread will be able to access objects created on the UI thread.

If you need to access a UI object from another thread you need the UI thread Dispatcher, and then you can use this to invoke calls on the UI thread.

I've spent many hours in frustration of similar problems to this - believe me.. Check out this question - it gave me a lot of useful information on the subject.

Community
  • 1
  • 1
stiank81
  • 25,418
  • 43
  • 131
  • 202
  • But, after I change the code to CroppedBitmap cb = new CroppedBitmap(new WriteableBitmap(bf), new Int32Rect(1, 1, 5, 5)); no exception. why? they are still in different thread. – zunyite Apr 28 '10 at 11:40
  • Sorry - I'm not sure about that. Haven't used WriteableBitmap. Maybe the documentation can give you a hint? http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspx – stiank81 Apr 28 '10 at 13:12
3

You can look through this classes in reflector. Exception will rise in cb.Freeze(). In

CroppedBitmap cb = new CroppedBitmap(bf, new Int32Rect(1,1,5,5));

case constructor did something like this:

this.this.Source = source;

So source wasn't created in current thread, and so exception will rise. In

new WriteableBitmap(bf)

case, constructor synchronize with bf object and new source is created in current thread, so, no exceptions will rise. If you are interested in In Depth details, you can always reflect base libraries with Reflector :)

Andrew
  • 1,102
  • 1
  • 8
  • 17
  • I'v updated mycode, please see my new code 2, CroppedBitmap cb = new CroppedBitmap(wb, r); wb now in different thread, but NO exception. why ? – zunyite Apr 28 '10 at 15:13
  • 1
    No exceptions was thrown because WriteableBitmap was created to work with multiple threads (it even has lock and unlock methods) http://msdn.microsoft.com/en-en/library/system.windows.media.imaging.writeablebitmap(VS.90).aspx And CroppedBitmap - isn't, actually at first, i thought that only BitmapFrame doens't like call from multiple threads, i was wrong, CroppedBitmap doesn't like it to :) – Andrew Apr 28 '10 at 16:53
  • I'v updated mycode again, please see my new code 3, now the I replace WritableBitmap with BitmapFrame, It still works. – zunyite Apr 28 '10 at 18:34
  • 1
    BitmapFrame at first seems the same but actually different constructors created different classes (BitmapFrame is abstract actually), bf is BitmapFrameDecode, and bf2 is BitmapFrameEncode, they are internal classes derived from BitmapFrame, so one is support access from other thread when the other isn't. Actually some properties have complex getter that throw exception, so if you are interested in it, go go go Disassemble :) – Andrew Apr 28 '10 at 20:53
  • Thanks, I'd try to trace the code with reflector, but I have no idea how to tell if a bitmap class support thread or not. – zunyite Apr 29 '10 at 08:28
  • For this i think debugger is much better then reflector :) Reflector helps you to understand what happend and why, but with debugger you can just look through properties and find out is they are good or throw exception, but you have to create code example though :) – Andrew Apr 29 '10 at 08:56
2

I had the same issue and fixed the problem by creating my UIElement in the UI thread by using its dispatcher (that can be accessed by Application.Current.Dispatcher ).

Before:

public static UIElement CreateUIElement()
{
    UIElement element = new UIElement();
    //Initialized the UIElement here
    return element;
}

This code caused an XamlParseException as it was called in a different thread than the UI thread.

My working solution :

public static UIElement CreateUIElement()
{
    UIElement element = null;
    Application.Current.Dispatcher.Invoke(
       System.Windows.Threading.DispatcherPriority.Normal, new Action(
          delegate()
          {
              element = new UIElement();
              // Initialize your UIElement here
          }));
    return element;
}

More info about the dispatcher can be found here http://tech.pro/tutorial/800/working-with-the-wpf-dispatcher

Good luck

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Bahamut
  • 195
  • 1
  • 6