Acerca de:

Este blog contiene los códigos, ejemplos y bases de datos que he usado cuando aprendía acerca de algún tema específico. En lugar de borrarlos (una vez dominado ya el tema), he decidido publicarlos :)
Mostrando entradas con la etiqueta threading. Mostrar todas las entradas
Mostrando entradas con la etiqueta threading. Mostrar todas las entradas

jueves, 9 de noviembre de 2023

Hilos con C#, corregido

Continuando con mi entrada anterior, el detalle que hay que corregir es el siguiente: no se puede modificar la interfaz gráfica desde otro hilo. Las interfaces gráficas de .Net y Java no son thread-safe, incluso estos frameworks arrojarán una excepción si detectan que se está intentando modificar la interfaz gráfica desde otro hilo que no haya sido el que la creó.

Como ejemplo, voy a añadir un par de líneas al final de las funciones Hilo1() e Hilo2() en el código que dejé como ejemplo:






En muchas aplicaciones que modifican la interfaz gráfica es necesario llamar a las funciones Refresh() de los controles. Después de este cambio, la aplicación arroja esta excepción:



La manera correcta, en C#, de modificar la interfaz gráfica desde otro hilo es usando las funciones Invoke() o BeginInvoke() de la plataforma .Net. 

Por otro lado, es una mala práctica llamar a la función Abort() de un hilo. Si se desea que un hilo finalice junto a la aplicación que lo creó se debe declarar como hilo de background, eso se hace en una línea de código:

p2.IsBackground = true;

El código corregido es:

using System;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Threading; 


namespace WindowsApplication1

{

    public partial class Form1 : Form

    {

        Thread p1;

        Thread p2;

        byte r, g;

        bool b1, b2;

        

        public Form1()

        {

            InitializeComponent();

        }


        private void Form1_Load(object sender, EventArgs e)

        {

            r = 0; g = 255; b1 = false; b2 = true; 


            p1 = new Thread(new ThreadStart(Hilo1));

            p2 = new Thread(new ThreadStart(Hilo2));


            p1.IsBackground = true;

            p2.IsBackground = true;


            p1.Start();

            p2.Start(); 

        }

        

        public void Hilo1() {

            while (true)

            {


                Thread.Sleep(10);


                if (r >= 0 && r <= 255 && b1 == false)

                {

                    r++;

                    if (r == 255)

                        b1 = true;

                }


                if (r >= 0 && r <= 255 && b1 == true)

                {

                    r--;

                    if (r == 0)

                        b1 = false;

                }


                if (this.InvokeRequired)

                {

                    this.BeginInvoke((Action)(() =>

                    {

                        pictureBox1.BackColor = Color.FromArgb(r, 80, 100);

                        pictureBox1.Refresh();

                    }));

                }

            }

       }


        public void Hilo2() {

            while (true)

            {

                Thread.Sleep(10);


                if (g >= 0 && g <= 255 && b2 == false)

                {

                    g++;


                    if (g == 255)

                        b2 = true;

                }


                if (g >= 0 && g <= 255 && b2 == true)

                {

                    g--;


                    if (g == 0)

                        b2 = false;

                }


                if (this.InvokeRequired)

                {

                    this.BeginInvoke((Action)(() =>

                    {

                        pictureBox2.BackColor = Color.FromArgb(100, g, 80);

                        pictureBox2.Refresh();

                    }));

                }

            }

        }


        private void Form1_FormClosed(object sender, FormClosedEventArgs e)

        {

        }

    }

}

 

El nuevo ejemplo se puede descargar de aquí.

viernes, 29 de septiembre de 2023

Hilos con C# (C Sharp)

Otro tutorial viejito, muy viejito, hay un detalle qué arreglar en el código, pero eso lo voy a explicar en una siguiente entrada:

¡Hilos! Visual Basic 6 no los permite, y programarlos en Borland C++ es muy engorroso (personalmente odio tener que estar programando archivos .h y el Borland los necesita para poder usar los hilos). En este tutorial voy a usar C#.

Para el programita que voy a mostrar a continuación hay que abrir el Visual C# (yo uso el 2005), crear un nuevo proyecto tipo Windows Application y en el formulario que aparece, ponerle dos Pictureboxes: pictureBox1 y pictureBox2. Al final, debe verse así:

 

Y luego se le mete este código:

using System;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Threading;

 

namespace WindowsApplication1

{

    public partial class Form1 : Form

    {

        Thread p1;

        Thread p2;

        byte r, g;

        bool b1, b2;

       

        public Form1()

        {

            InitializeComponent();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

            r = 0; g = 255; b1 = false; b2 = true;

            p1 = new Thread(new ThreadStart(Hilo1));

            p2 = new Thread(new ThreadStart(Hilo2));

            p1.Start();

            p2.Start();

        }

       

        public void Hilo1() {

            while (true)

            {

                Thread.Sleep(10);

 

                if (r >= 0 && r <= 255 && b1 == false)

                {

                    r++;

                    if (r == 255)

                        b1 = true;                   

                }

 

                if (r >= 0 && r <= 255 && b1 == true)

                {

                    r--;

                    if (r == 0)

                        b1 = false;

                }

                pictureBox1.BackColor = Color.FromArgb(r, 80, 100);

            }

       }

 

        public void Hilo2() {

            while (true)

            {

                Thread.Sleep(10);

 

                if (g >= 0 && g <= 255 && b2 == false)

                {

                    g++;

 

                    if (g == 255)

                        b2 = true;

                }

 

                if (g >= 0 && g <= 255 && b2 == true)

                {

                    g--;

 

                    if (g == 0)

                        b2 = false;

                }     

                pictureBox2.BackColor = Color.FromArgb(100, g, 80); 

            }                  

        }

 

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)

        {

            p1.Abort();

            p2.Abort();

        }

    }

}

 

Lo que se debe entender primero es que existen dos hilos: "p1" y "p2" (o dos objetos de la clase "Thread", en inglés "Hilo") cada uno asociado a un método: A "p1" le corresponde "Hilo1" y a "p2" le corresponde "Hilo2". Lo que "p1" y "p2" permiten es que ambos métodos (Hilo1 e Hilo2) se ejecuten de manera simultánea.

Todas las otras variables en el programa son de control. Los hilos empiezan a ejecutarse al llamar al método "Start" y se terminan con "Abort". El C# no permite llamar de nuevo a "Start" para un hilo que ya se está ejecutando.

Lo que hace el programa simplemente es variar el color de ambos PictureBoxes en un bucle que jamás termina, para ello se usa el comando CualquierPictureBox.BackColor = Color.FromArgb(100, g, 80);

También uso el método Thread.Sleep que detiene la ejecución del hilo durante el tiempo en milisegundos que va entre paréntesis. Esto es para que los colores varíen despacio y se puedan apreciar.

Cada vez que dentro de un método se usa la palabra "Thread" ésta se refiere al hilo que está asociado a él.

Una buena página para aprender hilos en C# es ésta: https://www.albahari.com/

Y acá se puede descargar mi ejemplo de los PictureBoxes.

jueves, 23 de abril de 2020

Los Filósofos Comiendo en Java con Netbeans

Una explicación de este problema está en este enlace. Se trata de un problema de concurrencia: en una mesa circular hay cinco filósofos con cinco platos y cinco cucharas al lado derecho de cada uno. Todos deben poder comer sin fastidiar al filósofo de al lado. A su vez, cada filósofo emplea un tiempo en dejar de comer y ponerse a pensar.
Aquí los filósofos son hijos y las cucharas son recursos compartidos. En mi caso, las cucharas son accesos exclusivos a archivos compartidos.
Para evitar problemas de deadlock (un hilo no libera un recurso que otro necesita, mientras que el segundo hilo mantiene un recurso que el primero necesita...) o starvation (todos los recursos compartidos están ocupados), incluí varias reglas y sincronismos, por ejemplo que los filósofos sólo podían coger las cucharas a su izquierda (identificadas por sus índices). La mesa circular se simula jugando con los índices, el filósofo al que se le asigna el índice cero recibe la última y la primera cuchara:

La clase Spoon recibe como parámetros el índice del filósofo al que ha sido asignada, el número total de filósofos y los nombres de los archivos. La clase Philosopher recibe como parámetros dos objetos Spoon, el lugar que ocupa en la mesa (es decir su índice en el array), y su nombre. Es en esta clase donde se realizan las funciones multihilo.

Los archivos aparecerán en la carpeta del proyecto:


Nunca aparecerán más de cuatro archivos file1x.tmp por la regla que añadí: si se ocupan todas las cucharas, el filósofo debe soltarlas:


El proyecto completo se puede descargar de aquí, corre en java 8.