Silverlight 3D y los threads

Un pequeño test.

Vamos a abrir el ejemplo de bloom (que podéis encontrar en C:/Program Files (x86)/Microsoft SDKs/Silverlight/v5.0/Toolkit/Sep11/Source/Sample source code.zip/Xna después de instalar el Silverlight toolkit). Ejecutadlo, y observad el tanque brillante.

Ahora vamos a abrir MainPage.xaml.cs,  buscamos el método OnDraw, y añadimos esta linea:

int test= SettingsCombo.SelectedIndex;

Ejectuad el ejemlo otra vez:

CrossAppDomainMarshaledException was unhandled
System.UnauthorizedAccessException: Invalid cross-thread access.
at MS.Internal.XcpImports.CheckThread()
at System.Windows.DependencyObject.GetValueInternal(DependencyProperty dp)
at System.Windows.FrameworkElement.GetValueInternal(DependencyProperty dp)
at System.Windows.Controls.Primitives.Selector.get_SelectedIndex()
at Bloom.MainPage.OnDraw(Object sender, DrawEventArgs e)

¿Qué está pasando?

La clase tradicional de XNA Game no es multithreaded. Sin embargo, es obvio que nos podemos crear nuestros propios threads, pero la programación paralela está muy óptimizada, así que si no queremos hacer nada especial, no tenemos que preocuparnos por las complejidades de esto.

Silverlight funciona de la misma manera. Tiene un sólo thread para la interfaz de usuario que es el responsable de todo el código de usuario, manegadores de eventos, y el renderizado XAML. Sin embargo, Silverlight 5 añade un montón de mejoras con respecto a la arquitectura de threads que se preocupan del rendering en la GPU (aunque el software de rendering se sigue ejecutando en el thread de la UI). Una de las ventajas de esta arquitectura es que aunque el thread de la UI se quede procesando operaciones XAML, consiguiendo una interfaz de usuario más suave. La contrapartida es que la programación con thread es muy dura, pero como todo nuestro código se ejecuta en el thread de la UI, no tenemos que preocuparnos de todo esto normalmente. Los gnomos del equipo de Silverlight se han preocupado de toda la sincronización necesaria para que el thread de composición siga trabajando sin que el cliente tenga que preocuparse de su código se ejecute de forma paralela.

Entrando en el DrawingSurface...

Al contrario que todo Silverlight, que usa un modo de renderización XAML retenido, DrawingSurface ofrece una API 3D en modo inmediato. Como es renderizado con aceleración de GPU, el evento DrawingSurface.Draw debe ocurrir, no en el thread de la UI con el resto del código, sino en el thread interno de composición.

¡Hay que ser valientes! Aunque no hayamos creado nunca threads, el simple acto de trucar el método Draw hace que nuestro código de renderizado 3D pueda ejecutar código paralelo junto al cóidogo UI. Así que sin darnos cuenta hemos caido en el mundo de los bloqueos, interbloqueos, deadlocks, carreras de condición y todas esas cosas :-).

¿Qué significa esto para nosotros?

Si estamos haciendo un juego que sólo usa renerizado 3d sin ningún XAML en la UI, podemos poner todo el código (tanto el Update como el Draw) dentro del evento DrawingSurface.Draw. No importa que usemos una lógica centrada en el tiempo o no, esto significa que el thread de UI no será usado, así que no hay paralelismo del que preocuparnos y todo simplemente funcionará como esperamos.

Si combinamos XAML en el UI, debemos preocuparnos de sincronizar el acceso a cualquier dato que sea compartido entre ellos. En el ejemplo de bloom (que usa XAML para modificar los efectos de bloom) veremos que el código de renderizado no lee las opciones directamente de controles Silverlight. Sino que el Mainpage.xaml truca los eventos de cambio de esos controles (que se ejecutan en el thread UI) y copia los valores en campos que pueden ser accedidos de forma segura por el thread de composición. Si la estructura de datos fuese más compleja, necesitaríamos un bloqueo para coordinar el acceso.

Juan María Laó Ramos.

Artículo original.

Deja un comentario