3

I am new to C#, WPF & threading too. I am working on a web application with MVC5.

I got to know that I should do invoke on dispatcher when updating the UI elements in a thread other than main thread. 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.

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:

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." at canvasChart.Arrange when I add the following line in Chart class:

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:

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.

quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
vinmm
  • 277
  • 1
  • 3
  • 14
  • I feel like the code you've provided doesn't add up. You show two methods which use mutlithreading, and then the error comes up when using `distributionParams.rangeData` which doesn't seem to have anything to do with those two methods... Nor did you show how `distributionParams` is prepared in the main thread. – MBender Sep 09 '15 at 09:46
  • @Shaamaan: Sorry for missing things. I create Chart object and call Arrange on it. So, during arrange step it throws the exception. distributionParams is prepared like this: 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); Many more are on the list. But I am showing only 1 item. Please let me know you need any more information regarding this. – vinmm Sep 09 '15 at 10:01

2 Answers2

5

So, in DelegateBitmapGenerationToSTAThread you start new STA thread. Then you are trying to access DistributionParams.RangeData.barColor there, which is of type SolidColorBrush. You create those brushes in another (main UI) thread, that is why you are getting this exception. 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

    d.barColor = Colors.Blue;
    

then, create SolidColorBrush when necessary

 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. Such objects in WPF (and WinForms) have thread-affinity - they might be accessed only by one thread (thread on which they were created). 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. 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. If it's Freezable and frozen, class author guarantees that object cannot be changed any more (will throw exception on any change or whatever). Then it's safe to access such object from any thread, even if this object is UI-related.

Back to 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. If you use predefined brush (Brushes.Red for example) - it is frozen already for you, you cannot do Brushes.Red.Color = Colors.Blue.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • Banged on target. The problem was exactly what you said above. Really awesome find. Thanks. But can you please tell me why i should freeze the solid color brush created by me ? I am passing custom colors created by me as parameter to my SolidColorBrush and not predefined brushes in some cases. So, I should freeze because its a custom color i am creating ? – vinmm Sep 10 '15 at 03:13
  • Updated post with answer to your question. – Evk Sep 10 '15 at 06:47
0

You need to perform a Dispatcher.Invoke() call to switch to the UI thread. I'm under the weather right now, so I am just going to link to an SO answer to get you going.

Community
  • 1
  • 1
Martin Noreke
  • 4,066
  • 22
  • 34