2015-09-09 4 views
3

Я новичок в C#, WPF & Threading тоже. Я работаю над веб-приложением с MVC5.Получение исключения при вызове обновлений пользовательского интерфейса

Я узнал, что должен активировать диспетчер при обновлении элементов пользовательского интерфейса в потоке, отличном от основного потока. Но я точно не знаю, какие изменения мне нужно делать.

У меня есть метод 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; 
    } 

я получаю исключение «Не удается использовать DependencyObject, который принадлежит другому потоку, чем его родитель Freezable.» на холстеChart.Arrange, когда я добавлю следующую строку в класс диаграмм:

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

Пожалуйста, помогите мне решить эту проблему.

+0

Я чувствую, что код, который вы предоставили, не складывается. Вы показываете два метода, которые используют mutlithreading, а затем появляется ошибка при использовании 'distributionParams.rangeData', которая, похоже, не имеет никакого отношения к этим двум методам ... Также вы не показали, как' distributionParams' подготовлен в основной поток. – Shaamaan

+0

@ Шхамаан: Извините за недостающие вещи. Я создаю объект Chart и звоню. Таким образом, во время аранжировки это исключает. distributionParams подготовлен следующим образом: var d = new DistributionChartParameters.RangeData(); d = new DistributionChartParameters.RangeData(); d.range = "0-100"; d.distributionValue = 3; d.barColor = новый SolidColorBrush (Colors.Blue); rangeData.Add (d); В списке много других. Но я показываю только 1 пункт. Пожалуйста, дайте мне знать, что вам нужна дополнительная информация об этом. – vinmm

ответ

4

Итак, в DelegateBitmapGenerationToSTAThread вы запускаете новый поток STA. Затем вы пытаетесь получить доступ к DistributionParams.RangeData.barColor, который имеет тип SolidColorBrush. Вы создаете эти кисти в другом (основном UI) потоке, поэтому вы получаете это исключение. Что вы можете сделать, чтобы решить эту проблему:

  1. Попробуйте заморозить ваши кисти после создания.

    d.barColor = new SolidColorBrush(...); 
    d.barColor.Freeze(); 
    
  2. Используйте предопределенные кисти, они заморожены уже:

    d.barColor = Brushes.Blue; 
    
  3. Использовать цвет вместо SolidColorBrush

    d.barColor = Colors.Blue; 
    

затем создайте SolidColorBrush при необходимости

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

ОБНОВЛЕНИЕ, чтобы ответить на вопрос в вашем комментарии. SolidColorBrush может выглядеть невиновным, но это объект, связанный с UI, который определяет, как будет отображаться интерфейс.Такие объекты в WPF (и WinForms) имеют нить-сродство - к ним может обращаться только один поток (поток, на котором они были созданы). Почему такое ограничение? Потому что нелегко правильно и эффективно реализовать параллельные изменения свойствам таких элементов, которые влияют на рендеринг. В случае SolidColorBrush, представьте, что 10 потоков меняют цвет, а поток пользовательского интерфейса пытается отобразить все это. Поэтому, поскольку изменения разрешены - чтение также небезопасно.

Теперь, если ваш класс наследуется от Freezable, он обрабатывается WPF особым образом. Если он свободен и заморожен, автор класса гарантирует, что объект больше не может быть изменен (будет генерировать исключение при любом изменении или что-то еще). Тогда безопасно получить доступ к этому объекту из любого потока, даже если этот объект связан с UI.

Назад к SolidColorBrush. Когда вы создаете его (с любым цветом, даже с предопределенным), он не будет заморожен по умолчанию, и вы можете изменить его свойство Color в любое время. Если вы используете предопределенную кисть (Brushes.Red например) - она ​​уже заморожена для вас, вы не можете делать Brushes.Red.Color = Colors.Blue.

+0

Удары по цели. Проблема была именно в том, что вы сказали выше. Действительно удивительная находка. Благодарю. Но не могли бы вы рассказать мне, почему я должен заморозить созданную мной сплошную кисть? В некоторых случаях я передаю пользовательские цвета, созданные мной как параметр для SolidColorBrush, а не предопределенные кисти. Итак, я должен заморозить, потому что его собственный цвет я создаю? – vinmm

+0

Обновлено сообщение с ответом на ваш вопрос. – Evk

0

Вам необходимо выполнить вызов Dispatcher.Invoke() для переключения на поток пользовательского интерфейса. Прямо сейчас я под погодой, поэтому я просто собираюсь связаться с SO answer, чтобы вы начали.

Смежные вопросы