2012-03-26 9 views
6

Mi aplicación ejecuta algorythms pesado CPU para editar una imagen colocada en una ventana de WPF. Necesito que la edición se haga en un hilo de fondo. Sin embargo, intentar editar el BackBuffer de WritableBitmap en un subproceso que no sea de UI arroja InvalidOperationException.¿Cómo editar un WritableBitmap.BackBuffer en un subproceso que no sea de IU?

private WriteableBitmap writeableBitmap; 

    private void button1_Click(object sender, RoutedEventArgs e) 
    { 
     // Create WritableBitmap in UI thread. 
     this.writeableBitmap = new WriteableBitmap(10, 10, 96, 96, PixelFormats.Bgr24, null); 
     this.image1.Source = this.writeableBitmap; 

     // Run code in non UI thread. 
     new Thread(
      () => 
      { 
       // 'Edit' bitmap in non UI thread. 
       this.writeableBitmap.Lock(); // Exception: The calling thread cannot access this object because a different thread owns it. 

       // ... At this place the CPU is highly loaded, we edit this.writeableBitmap.BackBuffer. 

       this.writeableBitmap.Unlock(); 
      }).Start(); 
    } 

He leído docenas de manuales, todos ellos me dice que haga la edición BackBuffer en hilo de interfaz de usuario (es decir MSDN).

Cómo editar el WritableBitmap.BackBuffer en un subproceso sin interfaz de usuario sin copia/clonación de búfer inútil?

Respuesta

11

MSDN suggests escribiendo en el backbuffer en un hilo de fondo. Solo ciertas operaciones previas y posteriores a la actualización deben llevarse a cabo en el hilo de la interfaz de usuario.Así, mientras que el hilo de fondo está haciendo la actualización real, el hilo de interfaz de usuario es libre para hacer otras cosas:

 //Put this code in a method that is called from the background thread 
     long pBackBuffer = 0, backBufferStride = 0; 
     Application.Current.Dispatcher.Invoke(() => 
     {//lock bitmap in ui thread 
      _bitmap.Lock(); 
      pBackBuffer = (long)_bitmap.BackBuffer;//Make pointer available to background thread 
      backBufferStride = Bitmap.BackBufferStride; 
     }); 
     //Back to the worker thread 
     unsafe 
     { 
      //Carry out updates to the backbuffer here 
      foreach (var update in updates) 
      { 
       long bufferWithOffset = pBackBuffer + GetBufferOffset(update.X, update.Y, backBufferStride); 
       *((int*)bufferWithOffset) = update.Color; 
      } 
     } 
     Application.Current.Dispatcher.Invoke(() => 
     {//UI thread does post update operations 
      _bitmap.AddDirtyRect(new System.Windows.Int32Rect(0, 0, width, height)); 
      _bitmap.Unlock(); 
     }); 
+0

No creo que este sea el enfoque correcto. Las dos llamadas Invoke se bloquearán por más tiempo que el tiempo que tomaría escribir el búfer. – Brannon

+0

Estoy totalmente de acuerdo con los escenarios en los que la actualización del búfer de píxeles es un proceso rápido. Si la actualización del búfer implica algún cálculo caro (es decir, que cuesta más que las dos llamadas de invocación), lo anterior aún debería ser un enfoque perfectamente viable. – LOAS

0

En WPF las llamadas de hilos cruzados se realizan utilizando la clase Dispatcher.

En su caso en el subproceso sin interfaz de usuario, necesita obtener la instancia del distribuidor de la secuencia donde se crea WritableBitmap.

En esa despachador luego llamar a Invoke (o BeginInvoke si quieres que Asynchron)

invocación a continuación, llama a una función donde el delegado BackBuffer se edita

+0

Pero, ¿en qué hilo se ejecutará una función de delegado? ¿El hilo de la interfaz de usuario? Algún hilo aleatorio de ThreadPool? ¿O que? –

2

Simplemente no se puede escribir en BackBuffer de un no interfaz de usuario hilo.

Además de lo que dijo Klaus78, sugeriría el siguiente enfoque:

  1. Realizar asíncrono código "edición de mapa de bits" en una memoria intermedia separada (por ejemplo byte[]) en un hilo ThreadPool por medio de QueueUserWorkItem. No cree un nuevo subproceso cada vez que necesite realizar una operación asincrónica. Para eso se creó ThreadPool.

  2. Copie el búfer editado por WritePixels en el despachador de WriteableBitmap. No es necesario bloquear/desbloquear.

Ejemplo:

private byte[] buffer = new buffer[...]; 

private void UpdateBuffer() 
{ 
    ThreadPool.QueueUserWorkItem(
     o => 
     { 
      // write data to buffer... 
      Dispatcher.BeginInvoke((Action)(() => writeableBitmap.WritePixels(..., buffer, ...))); 
     }); 
} 
+0

He publicado 'new Thread()' en mi pregunta solo para indicar que la operación debe realizarse en algún otro hilo (aleatorio). –

+0

También en mi pregunta afirmé que no quiero crear ningún búfer, pero olvidé mencionar que mi mapa de bits es enorme. Es por eso que su enfoque un poco no es el que necesito. :) –

+0

Sí, pero me temo que de lo contrario la respuesta a su pregunta simplemente es: no puede. – Clemens

3

Como dijo Clemens, esto es imposible.

Tiene tres opciones:

1) introducir las modificaciones en un búfer y blit cuando haya terminado como sugiere Clemens.

2) Realice la edición en porciones muy pequeñas y prográmelas con una buena prioridad en el hilo de la GUI. Si mantiene los trozos de trabajo lo suficientemente pequeños, la GUI seguirá siendo receptiva, pero obviamente esto complica el código de edición.

3) Combine 1 & 2. Edite trozos pequeños en otro subproceso, luego rellene cada trozo a medida que se completa. Esto mantiene la GUI sensible sin usar memoria para un buffer de respaldo completo.

+0

Sí. El segundo enfoque es difícil de implementar, mientras que el primero se puede hacer fácilmente usando http://writeablebitmapex.codeplex.com/. Hay una función Blit rápida y agradable. Y el tercer enfoque es el mejor, por supuesto, sin embargo, no tuve tiempo de mejorar tanto nuestra aplicación. –

+0

La sugerencia n. ° 1 no funcionó porque 'Dispatcher.BeginInvoke (... writeableBitmap.WritePixels (..., buffer, ...));' toma demasiado tiempo. Como resultado, la aplicación falla mucho. Además, hacer cosas pesadas en la CPU en el hilo de la interfaz de usuario parece aún más receptivo. Ahora intentaré volver a implementar WritableBitmap usando dos Bitmaps regulares. –

0

he implementado el siguiente, basado en this answer:

En el modelo de vista, hay una propiedad como éste, que está unido a la fuente de la imagen en XAML:

private WriteableBitmap cameraImage; 
private IntPtr cameraBitmapPtr; 
public WriteableBitmap CameraImage 
{ 
    get { return cameraImage; } 
    set 
    { 
     cameraImage = value; 
     cameraBitmapPtr = cameraImage.BackBuffer; 
     NotifyPropertyChanged(); 
    } 
} 

el uso de una propiedad significa que si los cambios WritableBitmap, por ejemplo, debido a la resolución, se actualizará en la Vista y también se construirá un nuevo IntPtr.

La imagen se construye cuando sea apropiado:

CameraImage = new WriteableBitmap(2448, 2048, 0, 0, PixelFormats.Bgr24, null); 

En el hilo de actualización, una nueva imagen se copia en, por ejemplo, usando:

[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] 
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length); 

que haría

CopyMemory(cameraImagePtr, newImagePtr, 2448 * 2048 * 3); 

No podría ser una mejor función para este ...

En el mismo hilo, después de la copia:

parent.Dispatcher.Invoke(new Action(() => 
{ 
    cameraImage.Lock(); 
    cameraImage.AddDirtyRect(new Int32Rect(0, 0, cameraImage.PixelWidth, cameraImage.PixelHeight)); 
    cameraImage.Unlock(); 
}), DispatcherPriority.Render); 

donde parent es el Control/Ventana con la Imagen.

Cuestiones relacionadas