因为不同的线程拥有此对象,所以调用线程无法访问该对象。

20

为什么我无法在下面的代码中创建CroppedBitmap?我得到了一个异常:

因为不同的线程拥有它,所以调用线程无法访问此对象。

如果我将代码更改为

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

异常消失了?为什么?

下面是代码1,在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);
         }
    );
}

代码 2,有效:

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

代码 3,不需要使用 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);
    }
);

1
BitmapFrame 闻起来像是一个 UI 类,而且它是在工作线程上创建的,然后在 UI 线程上使用... - Aviad P.
4个回答

16

以下代码可能会帮助您解决从另一个线程更新GUI元素的问题:

模块级别

delegate void updateCallback(string tekst);

这是更新元素的方法:

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

我使用它时出现了错误: {"无法使用属于不同线程的父Freezable的DependencyObject。"} - NoWar
出色的实现。易于使用。 - MrWuf

7

在使用WPF时,请注意如果您在一个线程中创建了UI对象,则无法从另一个线程访问它。您的UI对象应该(通常)在UI线程中创建,然后您需要在稍后通过UI线程来访问它们。任何其他线程都将无法访问在UI线程上创建的对象。

如果您需要从另一个线程访问UI对象,则需要UI线程的Dispatcher,然后您可以使用它来调用UI线程上的方法。

我曾经花费很多时间遇到类似的问题而感到沮丧 - 相信我..请查看这个问题 - 它为我提供了许多有关此主题的有用信息。


但是,当我将代码更改为CroppedBitmap cb = new CroppedBitmap(new WriteableBitmap(bf), new Int32Rect(1, 1, 5, 5));时,没有异常。 为什么呢? 它们仍然在不同的线程中。 - zunyite
抱歉 - 我不确定。我没有使用过WriteableBitmap。也许文档可以给你一些提示?http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspx - stiank81

3
您可以在反射器中查看这些类。在cb.Freeze()中会出现异常。
CroppedBitmap cb = new CroppedBitmap(bf, new Int32Rect(1,1,5,5));

案例的构造函数大致是这样做的:

this.this.Source = source;

所以源代码并非在当前线程中创建,因此会引发异常。在

new WriteableBitmap(bf)

case, constructor与bf对象同步,并且新的源代码在当前线程中创建,因此不会出现任何异常。

如果您对深入细节感兴趣,您可以使用Reflector反射基础库:)


我已更新了我的代码,请查看我的新代码2,CroppedBitmap cb = new CroppedBitmap(wb, r); wb现在在不同的线程中,但没有异常。为什么? - zunyite
1
没有抛出任何异常,因为WriteableBitmap被创建来在多个线程上工作(它甚至有锁定和解锁方法)。 http://msdn.microsoft.com/en-en/library/system.windows.media.imaging.writeablebitmap(VS.90).aspx 而CroppedBitmap并不是,实际上,起初我认为只有BitmapFrame不喜欢从多个线程调用,但我错了,CroppedBitmap也不喜欢。 - Andrew
我又更新了我的代码,请查看我的新代码3,现在我用BitmapFrame替换了WritableBitmap,它仍然可以工作。 - zunyite
1
BitmapFrame 一开始看起来相同,但实际上不同的构造函数创建了不同的类(BitmapFrame 实际上是抽象的),bf 是 BitmapFrameDecode,bf2 是 BitmapFrameEncode,它们是从 BitmapFrame 派生的内部类,因此一个支持其他线程的访问,而另一个不支持。实际上,一些属性具有复杂的 getter,可能会抛出异常,如果你对此感兴趣,那就去去去反汇编 :) - Andrew
谢谢,我会尝试使用反编译工具来跟踪代码,但我不知道如何判断位图类是否支持线程。 - zunyite
对于这个问题,我认为调试器比反射器更好 :) 反射器可以帮助你理解发生了什么以及为什么会这样,但是使用调试器,你可以查看属性并找出它们是否良好或抛出异常,不过你需要创建代码示例 :) - Andrew

2

我曾经遇到过同样的问题,通过在UI线程中使用其调度程序(可以通过Application.Current.Dispatcher访问)创建我的UIElement来解决了这个问题。

之前:

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

这段代码因为在UI线程之外的不同线程中调用而引起了XamlParseException异常。

我的解决方案:

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

关于调度程序的更多信息可以在这里找到 http://tech.pro/tutorial/800/working-with-the-wpf-dispatcher

祝你好运


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接