简体   繁体   English

调用UI更新时获取异常

[英]Getting exception when calling UI updates

I am new to C#, WPF & threading too. 我是C#,WPF和线程的新手。 I am working on a web application with MVC5. 我正在使用MVC5开发Web应用程序。

I got to know that I should do invoke on dispatcher when updating the UI elements in a thread other than main thread. 我知道我应该在更新主线程以外的线程中的UI元素时调度调度程序。 But i am not exactly getting to know the changes I need to do. 但我并不完全了解我需要做的改变。

I have a method GetBitmapForDistributionChart which does UI updation like this. 我有一个GetBitmapForDistributionChart方法,它可以像这样进行UI更新。

public byte[] GetBitmapForDistributionChart(int width, int height, DistributionChartParameters disrtibutionParams)
    {
        // delegate control instantiation to STA Thread
        return DelegateBitmapGenerationToSTAThread(() =>
        {
            Chart canvasChart = new Chart(languageService);
            canvasChart.Width = width;
            canvasChart.Height = height;

            canvasChart.Measure(new Size(width, height));
            canvasChart.Arrange(new Rect(0, 0, width, height));

            return GenerateBitmapFromControl(canvasChart, width, height);
        });
    }

where definition of DelegateBitmapGenerationToSTAThread looks like this: DelegateBitmapGenerationToSTAThread的定义如下所示:

private byte[] DelegateBitmapGenerationToSTAThread(Func<byte[]> controlBitmapGenerator)
    {
        byte[] imageBinaryData = null;

        Thread thread = new Thread(() =>
        {
            var renderer = new BitmapCreator(this.languageService);
            imageBinaryData = controlBitmapGenerator();
        });

        //Set the thread to STA
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

        //Wait for the thread to end
        thread.Join();

        return imageBinaryData;
    }

I get an exception "Cannot use a DependencyObject that belongs to a different thread than its parent Freezable." 我得到一个异常“不能使用属于与其父Freezable不同的线程的DependencyObject。” at canvasChart.Arrange when I add the following line in Chart class: 在canvasChart.Arrange,我在Chart类中添加以下行:

rect.Fill = distributionParams.rangeData.ElementAt(i).barColor;

which is in main thread. 这是主线程。

If i change the same line to something which doesn't depend on right hand side class then it works. 如果我将同一行更改为不依赖于右侧类的东西,那么它可以工作。

Like, rect.Fill = new SolidColorBrush(Colors.Red);

I am not aware on how to fix this problem. 我不知道如何解决这个问题。

Note: Also I get exception "The calling thread cannot access this object because a different thread owns it." 注意:我也得到异常“调用线程无法访问此对象,因为另一个线程拥有它。” when trying to do this: 当试图这样做:

rect.Fill = new SolidColorBrush(distributionParams.rangeData.ElementAt(i).barColor.Color);

distributionParams structure looks like this: distributionParams结构如下所示:

public struct DistributionParams
{
    public struct RangeData
    {
        public string range;
        public double distributionValue;
        public SolidColorBrush barColor;
    }
    public List<RangeData> rangeData;
}

Please help me fix this problem. 请帮我解决这个问题。

So, in DelegateBitmapGenerationToSTAThread you start new STA thread. 因此,在DelegateBitmapGenerationToSTAThread中启动新的STA线程。 Then you are trying to access DistributionParams.RangeData.barColor there, which is of type SolidColorBrush. 然后你试图访问DistributionParams.RangeData.barColor,它是SolidColorBrush类型。 You create those brushes in another (main UI) thread, that is why you are getting this exception. 您在另一个(主UI)线程中创建这些画笔,这就是您获得此异常的原因。 What you can do to solve that: 你可以做些什么来解决这个问题:

  1. Try to freeze your brushes after creation. 创建后尝试冻结画笔。

     d.barColor = new SolidColorBrush(...); d.barColor.Freeze(); 
  2. Use predefined brushes, they are frozen already: 使用预定义的画笔,它们已被冻结:

     d.barColor = Brushes.Blue; 
  3. Use color instead of SolidColorBrush 使用颜色代替SolidColorBrush

     d.barColor = Colors.Blue; 

then, create SolidColorBrush when necessary 然后,在必要时创建SolidColorBrush

 rect.Fill = new SolidColorBrush(d.barColor);

UPDATE to answer the question in your comment. 更新以回答您的评论中的问题。 SolidColorBrush might look innocent, but that is still UI-related object which defines how interface would be rendered. SolidColorBrush可能看起来很无辜,但这仍然是与UI相关的对象,它定义了如何呈现界面。 Such objects in WPF (and WinForms) have thread-affinity - they might be accessed only by one thread (thread on which they were created). WPF(和WinForms)中的此类对象具有线程关联性 - 它们可能只能由一个线程(创建它们的线程)访问。 Why such limitation? 为何如此限制? Because it's not easy to correctly and efficiently implement concurrent changes to the properties of such elements which affect rendering. 因为要正确有效地实现对影响渲染的元素属性的并发更改并不容易。 In case of SolidColorBrush, imagine 10 threads changing it's Color, and UI thread tries to render all that. 在SolidColorBrush的情况下,想象10个线程改变它的颜色,UI线程尝试渲染所有这些。 So because changes are allowed - reads are not safe too. 因此,因为允许更改 - 读取也不安全。

Now, if your class inherits from Freezable, it is treated in a special way by WPF. 现在,如果您的类继承自Freezable,WPF将以特殊方式处理它。 If it's Freezable and frozen, class author guarantees that object cannot be changed any more (will throw exception on any change or whatever). 如果它是Freezable并且被冻结,那么类作者保证对象不能再被更改(将在任何更改或其他任何内容上抛出异常)。 Then it's safe to access such object from any thread, even if this object is UI-related. 然后,从任何线程访问此类对象都是安全的,即使此对象与UI相关。

Back to SolidColorBrush. 回到SolidColorBrush。 When you create it (with any color, even predefined one), it's not frozen by default, and you can change its Color property any time. 当您创建它时(使用任何颜色,甚至是预定义的颜色),默认情况下它不会被冻结,您可以随时更改其Color属性。 If you use predefined brush (Brushes.Red for example) - it is frozen already for you, you cannot do Brushes.Red.Color = Colors.Blue. 如果你使用预定义的画笔 (例如Brushes.Red) - 它已经为你冻结了,你不能做Brushes.Red.Color = Colors.Blue。

You need to perform a Dispatcher.Invoke() call to switch to the UI thread. 您需要执行Dispatcher.Invoke()调用以切换到UI线程。 I'm under the weather right now, so I am just going to link to an SO answer to get you going. 我现在正处于天气状态,所以我只想链接到一个SO答案让你前进。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM