调用UI更新时出现异常

3

我是一名新手,对C#、WPF和线程也不熟悉。 我正在使用MVC5开发Web应用程序。

我了解到,当在主线程以外的线程更新UI元素时,应该在调度程序上执行invoke操作。但是我并不完全了解需要进行的更改。

我有一个名为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);
        });
    }

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

当我在Chart类中添加以下行时,在canvasChart.Arrange处出现“无法使用属于不同线程的DependencyObject而不是其父Freezable”的异常:
rect.Fill = distributionParams.rangeData.ElementAt(i).barColor;

这段代码在主线程中执行。

如果我把同一行代码改成不依赖于右侧类的内容,那么它就能正常工作。

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

我不知道如何解决这个问题。

注意: 当尝试执行以下操作时,也会出现异常信息“调用线程无法访问此对象,因为该对象是另一个线程拥有的。”

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

distributionParams结构如下:

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

请帮我解决这个问题。


我感觉你提供的代码有些不对劲。你展示了两种使用多线程的方法,但当使用 distributionParams.rangeData 时出现错误,这似乎与这两种方法没有任何关系...而且你也没有展示在主线程中如何准备 distributionParams - MBender
@Shaamaan: 抱歉漏掉了一些东西。我创建了一个Chart对象并调用Arrange方法。所以,在排列的过程中它抛出了异常。distributionParams是这样准备的:var d = new DistributionChartParameters.RangeData(); d = new DistributionChartParameters.RangeData(); d.range = "0-100"; d.distributionValue = 3; d.barColor = new SolidColorBrush(Colors.Blue); rangeData.Add(d);列表上还有很多其他项目,但我只展示了一个。如果需要更多信息,请告诉我。 - vinmm
2个回答

5

所以,在DelegateBitmapGenerationToSTAThread中,您启动了一个新的STA线程。然后您尝试在那里访问DistributionParams.RangeData.barColor,它是SolidColorBrush类型。您在另一个(主UI)线程中创建这些画笔,这就是为什么会出现此异常的原因。您可以采取以下措施来解决问题:

  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

    d.barColor = Colors.Blue;
    

然后,在必要时创建SolidColorBrush。

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

回答您在评论中提出的问题。 SolidColorBrush看起来可能很无害,但它仍然是与UI相关的对象,定义了界面如何呈现。在WPF(和WinForms)中,这些对象具有线程亲和性 - 它们只能由一个线程访问(创建它们的线程)。为什么有这样的限制?因为正确高效地实现并发更改这些影响呈现元素属性的操作不容易。以SolidColorBrush为例,想象一下10个线程同时更改其颜色,而UI线程尝试渲染所有这些更改。因此,因为允许更改 - 读取也不安全。

现在,如果您的类继承自Freezable,则WPF会以特殊方式处理它。如果它是Freezable并且已被冻结,则类作者保证对象不能再被更改(在任何更改或其他情况下都会引发异常)。然后,即使此对象与UI相关,从任何线程访问该对象也是安全的。

回到SolidColorBrush。当您创建它时(使用任何颜色,甚至是预定义的颜色),默认情况下它未被冻结,您可以随时更改其颜色属性。如果您使用预定义的画笔(例如Brushes.Red) - 它已经为您冻结,您无法执行Brushes.Red.Color = Colors.Blue。


命中目标。问题正是你上面所说的。真是太棒了。谢谢。但是,你能告诉我为什么我应该冻结我创建的纯色画刷吗?在某些情况下,我将由我创建的自定义颜色作为参数传递给我的SolidColorBrush,而不是预定义的画刷。所以,我应该冻结它,因为我正在创建自定义颜色? - vinmm
更新了帖子并回答了你的问题。 - Evk

0

你需要执行 Dispatcher.Invoke() 方法来切换到 UI 线程。我现在身体不舒服,所以我只会链接到一个 SO 答案 来帮助你。


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