|
M&P TBW se basa en el lenguaje XAML y su engine Dataflow.
Los BLOCKS M&P son UserControls XAML con algunas interfaces
adicionales requeridas por el Runtime de M&P (MPR).
La UI Thread
Si bien XAML es fundamentalmente un "general purpose
declarative language" muchos de sus elementos están
orientados a la interoperación con los engines de
visualización de WPF y Silverlight (SL). Estos engines de
visualización operan en un Thread propio que se denomina
"UI Thread" .
En el contexto de WPF/SL el Multi-threading es la
excepción, no la regla, ya que todas las operaciones realizadas
en Threads distintas de la UI Thread que interoperan con el
engine de visualización deben ser "encoladas" (queued) para su
ejecución en la UI Thread (ver WPF,
Silverlight y el Multi-threading).
En el contexto de visualización los Controles XAML que
componen la UI tienen "Properties" con una funcionalidad
extendida comparadas con las "Properties" normales de .NET.
Estas Properties extendidas se denominan "Dependency
Properties" (DP). En esta documentación nos referimos a las
Properties normales de .NET como "Common Properties" (CP).
En XAML todas las operaciones realizadas en Threads
distintas de la UI Thread que manipulen DPs deben ser
"encoladas" para su ejecución en la UI Thread.
EL Runtime de M&P es independiente de WPF y Silverlight y no
introduce restricciones al Multithreading. Como veremos, en M&P el Multithreadind es la regla.
M&P considera al engine de
visualización de WPF/SL un dispositivo
periférico de input-output (lo que en realidad es).
Para M&P el UI Thread es "un Thread más" (es sólo el Thread
que utiliza el engine de visualización).
Las DPs juegan un rol sumamente importante en el contexto de
visualización pero imponen muy severas restricciones al
Paralelismo, por lo que en los BLOCKS M&P se usan Common
Properties (CPs).
El Binding de XAML permite conectar DPs con CPs pero con la
restricción de que las "target" properties
deben ser DPs. En M&P
el BLOCK BBinding opera sin esta restricción y es, como
veremos, una pieza clave en el Procesamiento Paralelo.
En la sección "Implicit Parallelism" mencionamos que M&P
implementa el concepto de "paralelización implícita" con el
objetivo de permitir que el Desarrollador se pueda
desentender de los detalles del procesamiento paralelo para
concentrarse en los detalles específicos de la aplicación.
El BLOCK Thread
En M&P cada BLOCK, por default, opera en un Thread propio que se
denomina "BLKThread".
Parallel XAML
M&P obtiene un "Parallel XAML" extendiendo la funcionalidad de XAML
incorporando tres elementos:
Queued Properties (QPs)
Son Common Properties (CPs) acopladas al "Concurrent Queue"
contenido en los BLOCKS M&P. Mas abajo describimos su
funcionalidad en
detalle.
El BLOCK BBinding
Este es el BLOCK de Binding "universal" de M&P y es una pieza
fundamental del procesamiento paralelo.
La Clase BUIThread
Permite "encolar" sincrónicamente (EnqueueAndWait) o
asincrónicamente (Enqueue) operaciones en la UI Thread. Es
usada internamente en el BLOCK BBinding pero está disponible
para ser usada en cualquier BLOCK.
En la sección "Implicit Parallelism" mencionamos
también que los BLOCKS de M&P operan en paralelo
por default pero que el grado de paralelismo puede ser
limitado fácilmente por el desarrollador. Por ejemplo el
BLOCK BBinding tiene una propiedad booleana "Sync" que
permite indicar que el procesamiento debe ser sincrónico (el
valor default de Sync es false).
Queued Properties (QPs)
Para facilitar la comprensión de las QPs consideremos un
ejemplo en el que tres BLOCKS integran un simple "dataflow pipeline"
que debe procesar un conjunto de items I1, I2, ... IN.
Sin procesamiento paralelo cada item "captura" el pípeline
completo durante su procesamiento y solo un item puede ser
procesado a la vez. Pero supongamos que estamos operando en
un procesador "quad-core". En este caso tendríamos tres
procesadores libres en espera de trabajo. Esta la situación
típica con aplicaciones puramente secuenciales.
Pero imaginemos ahora que mediante multithreading
habilitemos el procesamiento paralelo. La situación ideal
seria:
y luego:
Pero, que sucedería si por alguna razón se produce la
situación siguiente ?:
En esta situación los ítems I3 e I4 estarían compitiendo por
el procesamiento del BLOCK [B].
Esta condición competitiva se denomina "race condition" y
debería resolverse mediante las técnicas habituales de
multithreading: locking, syncronization, etc.
Para evitar la condición competitiva la solución
en M&P consiste en establecer una "cola" (buffer) en la entrada de cada BLOCK. De esta manera en la imagen anterior el item I4
estaría en la cola del BLOCK [B] hasta que este termine de
procesar el item I3.
Si lográramos esconder la cola tendríamos una
"paralelización implícita" perfecta.
Pero como no podemos modificar el compilador ni el runtime
,NET no podemos ocultar completamente la cola y los
elementos asociados. Para minimizar la paralelización
explicita introducimos en M&P las Queued Properties (QPs).
Como veremos al final, el esquema resultante es mas fácil de
usar que de explicar.
Los elementos básicos del esquema son (en cada BLOCK):
- El Catalogo de Handlers (HC).
- El "Concurrent Queue" (CQ).
- El "BLOCK Thread" (BLKThread).
- El "Queued Property Processor" (BQPP).
Los tres primeros están ocultos y son manejados por el BQPP.
El
Catalogo de Handlers (HC) es un diccionario que contiene los delegates de los
métodos que deben invocarse para procesar los valores que
reciben las
diferentes QPs. La clave del diccionario es el nombre de la
QP.
El "Concurrent Queue" (CQ) es una cola FIFO
(queue) thread-safe que
puede ser cargada desde múltiples Threads y también ser
descargada desde múltiples Threads. El CQ
implementa el conocido pattern "Producer-Consumer"
y el único Consumer es el BLKThread.
El
"BLACK Thread" (BLKThread) es un Thread utilizado por el
BQPP para invocar (despachar) los delegates asociados con las diferentes
QPs.
El "Queued Property Processor" (BQPP) maneja la
operación del conjunto.
Internamente el BQPP al encontrar un valor en el tope del CQ lo
extrae y "despacha" el handler registrado en el HC (por el Desarrollador)
pasándole el valor. La ejecución del handler es sincrónica.
Todos los elementos del esquema se instancian automáticamente
al instanciarse el BLOCK que los contiene.
Cada BLOCK tiene una referencia al objeto BQPP en una
Property predefinida denominada QPP que el
desarrollador debe usar en la lógica del BLOCK.
La interface de BQQP disponible para
el Desarrollador es simple y consta de solo tres métodos:
interface IBQPP { void Enqueue(string propname, object value); void RegPCH(string propname, Action<object> propdel); void StartDispatching(); }
A continuación incluimos como ejemplo el código de un custom
BLOCK que implementa QPs y de un programa que interactúa con
el custom BLOCK.
El código contiene comentarios detallados. En MainWindow.xaml
puede verse la utilización del BLOCK BBinding.
ParallelBLOCK.cs
MainWindow.xaml
MainWindow.xaml.cs
El siguiente es un fragmento del código de ParallelBLOCK.cs:
public class ParallelBLOCK : BLOCK
{
public ParallelBLOCK()
{
// Registracion de delegates
QPP.RegPCH("PropI", PropIPCH);
QPP.RegPCH("PropS", PropSPCH);
}
public override void BStart()
{
if (BIsStarted)
return;
BIsStarted = true;
// Arranca el Dispatching
QPP.StartDispatching();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Input Queued Property
private int _PropI = -1; // <<<<<<<<<<<
public int PropI
{
set
{
// Encola el valor
QPP.Enqueue("PropI", value);
}
}
// Handler de la Property PropI
void PropIPCH(object value)
{
OutS = "@" + value;
}
// Input Queued Property
private String _PropS = ""; // <<<<<<<<<<<
public String PropS
{
set
{
// Encola el valor
QPP.Enqueue("PropS", value);
}
}
// Handler de la Property PropS
void PropSPCH(object value)
{
OutS = "" + value;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Output Property del BLOCK
private String _OutS = "";
public String OutS
{
get
{
return _OutS;
}
set
{
if (_OutS == value)
return;
_OutS = value;
OnPropertyChanged("OutS");
}
}
}
Ver también
Links
|