¿Cómo acceder a la información en un hilo con un temporizador?

Creé un formulario en C# donde hay una imagen que se está convirtiendo en una matriz booleana. Estoy tratando de generar un hilo con un temporizador donde la imagen se convierte 4 veces por segundo.

Cuando lo depuro, funciona, puedo rastrear los tics del temporizador. Pero cuando el formulario se está ejecutando, la aplicación se cierra sin dar un error.

Este es el script de inicialización:

form = new LoadForm();
form.Show();
form.BringToFront();

timer = new System.Threading.Timer(new TimerCallback(camTick), null, 0, 250);

Este es el tick que funciona:

private void camTick(Object myObject) 
{
    if (form.isRunning) 
    {
        bool[,] ar = form.getBoolBitmap(100);
    }
}

Esta es la función que carga y guarda el mapa de bits. En form1.cs

public bool[,] getBoolBitmap(uint threshold) {
        unsafe {
            Bitmap b = getBitmap();

            BitmapData originalData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

            bool[,] ar = new bool[b.Width, b.Height];

            for (int y = 0; y < b.Height; y++) {
                byte* Row = (byte*)originalData.Scan0 + (y * originalData.Stride);

                for (int x = 0; x < b.Width; x++) {
                    if ((byte)Row[x * 3] < threshold) {
                        ar[x, y] = false;
                    } else {
                        ar[x, y] = true;
                    }
                }
            }

            b.Dispose();

            return ar;
        }
    }

public Bitmap getBitmap() {
        if (!panelVideoPreview.IsDisposed) {
            Bitmap b = new Bitmap(panelVideoPreview.Width, panelVideoPreview.Height, PixelFormat.Format24bppRgb);
            using (Graphics g = Graphics.FromImage(b)) {
                Rectangle rectanglePanelVideoPreview = panelVideoPreview.Bounds;
                Point sourcePoints = panelVideoPreview.PointToScreen(new Point(panelVideoPreview.ClientRectangle.X, panelVideoPreview.ClientRectangle.Y));
                g.CopyFromScreen(sourcePoints, Point.Empty, rectanglePanelVideoPreview.Size);
            }

            return b;
        } else {
            Bitmap b = new Bitmap(panelVideoPreview.Width, panelVideoPreview.Height);
            return b;
        }
    }

preguntado el 03 de mayo de 12 a las 17:05

No veo evidencia de que esto funcione 4 veces por segundo. El evento se dispararía una vez cada 250 ms. También necesita invocar su control ya que está tratando de modificar un control dentro de un subproceso de temporizador. -

3 Respuestas

¿Esta imagen se almacena en un Control como PictureBox? Si es así, asegúrese de que solo está accediendo a él en el hilo del control. Verificar Control.Invoke ().

contestado el 03 de mayo de 12 a las 17:05

La devolución de llamada de System.Threading.Timer corre en un ThreadPool hilo. En esa devolución de llamada, está accediendo a un Form ejemplo. Simplemente no puedes hacer esto. bueno, tu puede, pero no funcionará correctamente. Fracasará de forma impredecible ya veces espectacularmente.

Utiliza System.Windows.Forms.Timer en lugar de. Haz que marque cada 4 segundos. En el Tick obtener los datos que necesite del formulario y luego páselo a otro subproceso para su posterior procesamiento.

En el siguiente código obtengo el Bitmap objeto llamando DrawToBitmap en el subproceso de la interfaz de usuario. luego paso el Bitmap a un Task donde se puede convertir en un bool[] en el fondo. Opcionalmente, puede devolver ese bool[] from the Task y luego llamar ContinueWith para transferirlo de nuevo al subproceso de la interfaz de usuario si es necesario (aunque parece que probablemente no necesite hacer eso).

private void YourWindowsFormsTimer_Tick(object sender, EventArgs args)
{
  // Get the bitmap object while still on the UI thread.
  var bm = new Bitmap(panelVideoPreview.ClientSize.Width, panelVideoPreview.ClientSize.Height, PixelFormat.Format24bppRgb);
  panelVideoPreview.DrawToBitmap(bm, panelVideoPreview.ClientRectangle);

  Task.Factory
    .StartNew(() =>
    {
      // It is not safe to access the UI here.

      bool[,] ar = ConvertBitmapToByteArray(bm);

      DoSomethingWithTheArrayHereIfNecessary(ar);

      // Optionally return the byte array if you need to transfer it to the UI thread.
      return ar;
    })
    .ContinueWith((task) =>
    {
      // It is safe to access the UI here.

      // Get the returned byte array.
      bool[,] ar = task.Result;

      UpdateSomeControlIfNecessaryHere(ar);

    }, TaskScheduler.FromCurrentSynchronizationContext());
}

Y también debes ConvertBitmapToByteArray se vería así ahora.

public unsafe bool[,] ConvertBitmapToBoolArray(Bitmap b, uint threshold) 
{
  BitmapData originalData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

  bool[,] ar = new bool[b.Width, b.Height];

  for (int y = 0; y < b.Height; y++) 
  {
    byte* Row = (byte*)originalData.Scan0 + (y * originalData.Stride);
    for (int x = 0; x < b.Width; x++) 
    {
      if ((byte)Row[x * 3] < treshold) 
      {
        ar[x, y] = false;
      } else 
      {
        ar[x, y] = true;
      }
    }
  }
  b.Dispose();
  return ar;
}

contestado el 04 de mayo de 12 a las 17:05

Hmm, lo siento, realmente no entiendo lo que estás haciendo. Esta es la función donde se toma la matriz booleana, la actualicé en la parte superior. Está tomado de una caja de imágenes que tiene una cámara web proyectada con las DLL de Expression Encoder. - tversteeg

@ThomasVersteeg: actualicé mi respuesta. Creo que entiendo lo que estás haciendo ahora... perdón por la confusión. - Brian Gedeón

Después de buscar en Internet, descubrí esto, necesitaba hacer otra función para devolver el bool[,], esto es lo que se me ocurrió:

public bool[,] invokeBitmap(uint treshold) {
        if (this.InvokeRequired) {
            return (bool[,])this.Invoke((Func<bool[,]>)delegate {
                return getBoolBitmap(treshold);
            });
        } else {
            return getBoolBitmap(treshold);
        }
    }

contestado el 04 de mayo de 12 a las 15:05

Si hiciera esto, el subproceso de trabajo no tendría sentido ya que no sucedería nada útil en él. - Brian Gedeón

Tal vez no me aclaré... esta no puede ser la respuesta correcta. Si lo hiciste de esta manera todo se estaría ejecutando en el subproceso de la interfaz de usuario. Podrías apostar por toda la idea de enhebrar y llamar getBoolBitmap en el subproceso de la interfaz de usuario para empezar. Sería mejor que hacer lo que propones aquí. - Brian Gedeón

Ah, pero funciona bastante bien ahora, así que está bien. - tversteeg

Genial... entonces deshazte del System.Threading.Timer y use System.Windows.Forms.Timer en lugar de. Que no tendrás que usar Invoke en absoluto. - Brian Gedeón

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.