none
Problema BackgroundWorker con un control Image RRS feed

  • Pregunta

  • Hola, tengo una aplicación WPF con un control Image que se asigna con un WriteableBitmap generado. Como el proceso de generación del WriteableBitmap es largo, he utilizado un subproceso para ello mediante BackgroundWorker. Según MSDN, se utiliza el evento BackgoundWorker.DoWork() del proceso para incluir las instrucciones que consumen tiempo. Despúes, cuando termina el proceso, mediante el evento BackgoundWorker.RunWorkerCompleted() se pueden actualizar los controles de la ventana sin que entren en conflicto por pertenecer a otro proceso diferente (la ventana). Sin embargo, esto no parece del todo cierto.

    Al compilar este código, la línea "Imagen.Source" lanza el siguiente error de compilación: "El subproceso que realiza la llamada no puede obtener acceso a este objeto porque el propietario es otro subproceso". Sin embargo, en las siguientes líneas donde se asignan las propiedades Foreground y Text del control TextBlock, que también son controles de un proceso diferente (la ventana), no hay ningún problema.

    public partial class BackgroundWorkerTest : Window
        {
            private BackgroundWorker backgroundWorker;
           
            public BackgroundWorkerTest()
            {
                InitializeComponent();
                backgroundWorker = ((BackgroundWorker)this.FindResource("worki"));
            }

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                CaptureScreen.IsEnabled = false;
                textBlock.Foreground = System.Windows.Media.Brushes.Green;
                textBlock.Text = "Capturando imagen. Espere por favor.....";

                ObjetoCaptura objetoCaptura = new ObjetoCaptura();
                backgroundWorker.RunWorkerAsync(objetoCaptura); // Aquí se inicia el subproceso.

           }

            // Este evento se dispara cuando se inicia el subproceso.
            private void worki_DoWork(object sender, DoWorkEventArgs e)
            {
                ObjetoCaptura objetoCaptura  = (ObjetoCaptura)e.Argument;
                objetoCaptura.Captura();
                WriteableBitmap wri = objetoCaptura.WriBitmapSubPro;
                e.Result = wri;
            }
           
            private void worki_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                 if (e.Cancelled)
                {
                    MessageBox.Show("Proceso cancelado.");
                }
                 else if (e.Error != null)
                 {
                     MessageBox.Show(e.Error.Message, "Error");
                 }
                 else
                 {
                     WriteableBitmap wri = (WriteableBitmap)e.Result;
                //this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate() { Imagen.Source = wri; }); Provoca el mismo error.
                     Imagen.Source = wri;
                     CaptureScreen.IsEnabled = true;
                     textBlock.Foreground = System.Windows.Media.Brushes.Red;
                     textBlock.Text = " Pulse el botón Capture Screen para capturar la pantalla";
                 }
            }
        }

        public class ObjetoCaptura
        {...}

    ¿Es posible que los tipos de controles Image no se admitan en los subprocesos, o hay que asignarlos de otra forma?

    saludos.

    jueves, 27 de enero de 2011 5:44

Respuestas

  • El editor de respuestas de este foro "¡¡ DA GUSTO !!". No pienso modificar el código por tercera vez. Definitivamente la acción de copiar y pegar el código no funciona bien, ni de lejos. El volver a picar el código desde cero otra vez para que quede bien es una tontería.

    Espero que esto no sea un problema por utilizar Internet Explorer 9 Beta, pues no lo he probado en otras versiones. Mejor, que lo arreglen los de Microsoft.

     

    • Marcado como respuesta gandiweb martes, 1 de febrero de 2011 15:20
    martes, 1 de febrero de 2011 14:54

Todas las respuestas

  • Que tal amigo!

    Si mal no recuerdo la sintaxis deberia hacerse algo asi cuando quieras asignar el source:

    this.Dispatcher.Invoke(DispatcherPriority.Send, () => { Imagen.BeginEdit(); Imagen.Source = wri; Imagne.EndEdit(); });

    Pruebalo y me dices como te fue, saludos!

    jueves, 27 de enero de 2011 7:18
  • Pues, tampoco va El Maiks, me da el mismo error en la compilación.

    Además, ni utilizando el BackgroundWorker, que sería modificando Imagen.source por:

    Imagen.BeginInit();  //Y  no BeginEdit() porque no existe para el tipo Image.
    Imagen.Source = wri;
    Imagen.EndInit(); //Y no EndEdit() porque tampoco existe.

    ni con DispatcherObject como tú dices, sustituyendo el BeginEdit() y quitando la directiva pragma (=>) que tampoco la reconoce.

     

    jueves, 27 de enero de 2011 8:06
  • ¡Hola!

    ¿Y la lectura de la letra pequeña? "Tenga cuidado para no manipular ningún objeto de la interfaz de usuario en el controlador de eventos DoWork"

    Si lo que "capturas" esta en la interface de usuario no lo puedes hacer. BackgroundWorker es para trabajar en segundo plano en procesos como acceso a base de datos, ...

    Igual deberías de planteartelo de otra forma. En el hilo principal capturas lo quedeseas y aprovechas el BackgroundWorker para lanzar un control busy.

    Saludos,

    jueves, 27 de enero de 2011 17:18
  • Hola, sigo con las pruebas sin ningún resultado positivo. He creado este código mucho más sencillo para realizar las pruebas con BackgroundWorker.

    <Window x:Class="WPF_BackgroundWorkerTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:cm="clr-namespace:System.ComponentModel;assembly=System"
            Title="MainWindow" Height="633 " Width="546">
        <Window.Resources>
           

         <cm:BackgroundWorker x:Key="worki"
             RunWorkerCompleted="worki_RunWorkerCompleted"
                                 DoWork="worki_DoWork"/>
        </Window.Resources>
       

         <Canvas>
            <Button Name="CaptureScreen" Content="Capture Screen" Margin="24,166,339,86" Height="57" Canvas.Top="-51" Width="71"
                             Click="Button_Click" Canvas.Left="-4" />
            <Image Name="Imagen" Canvas.Left="107" Canvas.Top="24" Height="269" Width="401" />
        </Canvas>
    </Window>

     

    public partial class MainWindow : Windows                                                                                            {   

           public BackgroundWorker backgroundWroker;

           {

                    InitializeComponent();

                  backgroundWorker = (BackgroundWorker)this.FindResources("worki"));

            }

            private void Button_Click(object sender, RoutedEventArgs e)

            {

                  CaptureScreen.IsEnabled = false;

                  backgroundWorker.RunWorkerAsync();

             }

             private void worki_DoWork(object sender, DoWorkEventArgs e)

             {

                   BitmapImage wri = new BitmapImage();

                   wri.BeginInit();

                  wri.UriSource = new Uri("vancouver.jog", UriKind.Relative);

                  wri.EndInit();

                  e.Result = wri;

              }

              private void worki_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

              {

                       Imagen.Source = (ImageSource)e.Result;

              }

    }

     y sigue dando el mismo error en la línea Imagen.Source: "El subproceso que realiza la llamada no puede tener acceso a este objeto porque el propietario es otro subproceso".

    viernes, 28 de enero de 2011 3:51
  • Otra prueba más con el enfoque del DispatcherObject:

    public partial class MainWindow : Window

    {

           public MainWindow()

           {

                 InitializeComponent();

           }

           private void Button_Click(object sender, RoutedEventArgs e)

           {

                 Thread subpro = new Thread(AsignaImagen);

                 subpro.Start();

           }

           private void AsignaImagen()

          {

                Thread.Sleep(TimeSpan.FromSeconds(5));

                BitmapImage wri = new BitmapImage();

                wri.BeginInit();

                wri.UriSource = new Uri("vancouver.jpg", UriKind.Relative);

                wri.EndInit();

                 this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate()

                 {

                         Imagen.Source = (ImageSource)wri;

                  });

             }

    }

     

    Nota: He probado ambos códigos con .NET 4 y .NET 3.5, y con Windows XP y Windows 7. Además en modo depuración y sin él (.exe)

    saludos

    y el mismo error en la misma línea. Entonces ¿cómo hay que hacer para asignar un control Image mediante un hilo o subproceso?
    viernes, 28 de enero de 2011 4:24
  • ¡Hola!

    He estado analizando tu código y comparando con otro que hice yo hace mucho pero el nBackgroundWorker en Silverlight, lo empleaba para grabar en un XML.

    Prueba lo siguiente:   backgroundWorker = (BackgroundWorker)this.FindResources("worki")); Lo pones en el evento del loaded  de la imagen del formulario.

    El objetivo es garantizar que esta realmente cargado que ya ha terminado un proceso antes de comenzar otro. Silverlight es muy pijotero para esas cosas. Supongo que también lo será WPF.

    Un saludo,

     

    lunes, 31 de enero de 2011 16:36
  • gracias por tu respuesta CorsarioVasco. No he llegado a probar lo que me dices, pero precisamente lo que no quiero es esperar a que un proceso termine antes de que comience otro, esto dejaría la UI congelada hasta que termine la captura de la pantalla. Pero no importa, he encontrado otra solución que me vale. Consiste en utilizar un ThreadPool, y si el subproceso no es extremadamente largo, se puede utilizar el resto de la UI  sin problemas mientras realiza el cálculo. Aquí está el código:

    private void

     

     

     

            textBlock.Foreground = System.Windows.Media.Brushes.Green;

     

     

     

     

     

            textBlock.Text = "Capturando imagen. Espere por favor.....";

     

            ObjetoCaptura objetoCaptura = new ObjetoCaptura();

     

            ThreadPool.UnsafeQueueUserWorkItem(CapturaPantalla, objetoCaptura);

     }

     private void CapturaPantalla(Object objetoCaptura)

     {

         Thread.Sleep(TimeSpan.FromSeconds(5));

     

        // Una tarea con prioridad Send para el Despachador. Esta es la prioridad máxima.

     

     

     

     

     

     

     

        this.

     

     

     

     

                ObjetoCaptura objetoCaptura2 = (ObjetoCaptura)objetoCaptura;

     

     

     

     

     }ObjetoCaptura {...}

    public class

     

     

    Y este otro código que quizá te pueda servir para el Silverlight:

    public

     

     

     

    MainWindow()

     {

           InitializeComponent();

          //Uri uri = new Uri("pack://application:,,,/vancouver.jpg");

     

     

     

     

     

         Uri uri = new Uri("http://z.about.com/d/animatedtv/1/0/1/m/simp2006_HomerArmsCrossed_f.jpg");

     

         ThreadPool.UnsafeQueueUserWorkItem(LoadImage, uri);

      }

     public void LoadImage(Object uri)

     {

          var decoder = new JpegBitmapDecoder(new Uri(uri.ToString()),

                          

         BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);

     

         decoder.Frames[0].Freeze();

         this.

    Dispatcher.Invoke(DispatcherPriority.Send, new Action<ImageSource>(SetImage), decoder.Frames[0]);

     }

     public void SetImage(ImageSource source)

     {

          Imagen.Source = source;

      }

    }

    Extraído de este enlace: http://stackoverflow.com/questions/716472/how-do-i-load-images-in-the-background

    Salu2

     

     

     

     

     

     

     

                Imagen.Source = objetoCaptura2.Wri;

     

                textBlock.Foreground = System.Windows.Media.Brushes.Red;

     

                textBlock.Text = "Pulse el botón Capture Screen para capturar la pantalla";

    Dispatcher.Invoke(DispatcherPriority.Send,

          (

    ThreadStart)delegate()

          {

        // Invoke() detiene su proceso hasta que el despachador ejecuta el código    // Las operaciones se procesan antes que otras operaciones asincrónicas.

     

    Button_Click(object sender, RoutedEventArgs e)

     {

     
    • Propuesto como respuesta CorsarioVasco martes, 1 de febrero de 2011 17:41
    martes, 1 de febrero de 2011 14:41
  • El editor de respuestas de este foro "¡¡ DA GUSTO !!". No pienso modificar el código por tercera vez. Definitivamente la acción de copiar y pegar el código no funciona bien, ni de lejos. El volver a picar el código desde cero otra vez para que quede bien es una tontería.

    Espero que esto no sea un problema por utilizar Internet Explorer 9 Beta, pues no lo he probado en otras versiones. Mejor, que lo arreglen los de Microsoft.

     

    • Marcado como respuesta gandiweb martes, 1 de febrero de 2011 15:20
    martes, 1 de febrero de 2011 14:54
  • private void
    
         textBlock.Foreground = System.Windows.Media.Brushes.Green; 
     
        textBlock.Text = "Capturando imagen. Espere por favor....."; 
     
        ObjetoCaptura objetoCaptura = new ObjetoCaptura(); 
     
        ThreadPool.UnsafeQueueUserWorkItem(CapturaPantalla, objetoCaptura); 
     }
    
     private void CapturaPantalla(Object objetoCaptura) 
    
     {
       Thread.Sleep(TimeSpan.FromSeconds(5)); 
    
      // Una tarea con prioridad Send para el Despachador. Esta es la prioridad máxima. 
    
       this.ObjetoCaptura objetoCaptura2 = (ObjetoCaptura)objetoCaptura; 
     
     }ObjetoCaptura {...}
    
    public class
     
    Y este otro código que quizá te pueda servir para el Silverlight:
    
    public
     
    MainWindow() {
    
        InitializeComponent();
    
       //Uri uri = new Uri("pack://application:,,,/vancouver.jpg"); 
     
       Uri uri = new Uri("http://z.about.com/d/animatedtv/1/0/1/m/simp2006_HomerArmsCrossed_f.jpg"); 
     
       ThreadPool.UnsafeQueueUserWorkItem(LoadImage, uri); 
     }
    
     public void LoadImage(Object uri) 
    
     {
       var decoder = new JpegBitmapDecoder(new Uri(uri.ToString()), 
    
       BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); 
     
       decoder.Frames[0].Freeze(); 
       this.
    
    Dispatcher.Invoke(DispatcherPriority.Send, new Action<ImageSource>(SetImage), decoder.Frames[0]); 
     }
    
     public void SetImage(ImageSource source) 
    
     {
    
       Imagen.Source = source; 
    
     }
    
    }
    
    Extraído de este enlace: http://stackoverflow.com/questions/716472/how-do-i-load-images-in-the-background
    
    
    Salu2
     
          Imagen.Source = objetoCaptura2.Wri; 
     
          textBlock.Foreground = System.Windows.Media.Brushes.Red; 
     
          textBlock.Text = "Pulse el botón Capture Screen para capturar la pantalla"; 
    
    Dispatcher.Invoke(DispatcherPriority.Send, 
       (
    	ThreadStart)delegate() 
       {
    
      // Invoke() detiene su proceso hasta que el despachador ejecuta el código  // Las operaciones se procesan antes que otras operaciones asincrónicas. 
    
    Button_Click(object sender, RoutedEventArgs e) 
     {
    
    

    ¡Hola!

     

    Me alegro que lo solucionaras y gracias por compartir tu solución con la comunidad. Creo que es un gran aporte.

    Tienes razón con el editor de respuestas, asi que si me lo permites me he tomado la libertad de identarlo ;-).

     

    Un saludo,

    martes, 1 de febrero de 2011 17:41
  • Pues IE9 tiene un problema con los editores de texto, no suelen funcionar bien.

    De todas formas el codigo no lo tienes que copiar y pegar en el editor del post directamente, tienes un botón con el simbolo </> en la barra del editor, que te abre una ventana popup para pegar codigo e indicarle el lang. de programación en el que está escrito.

    Con eso el código se pega correctamnte.

    De todas formas no creo que la solución sea romper la baraja, siempre se puede pedir ayuda.

    Un saludo


    MCTS .NET Framework 3.5 Windows Forms Application Development
    MCTS .NET Framework 3.5 Windows Presentation Foundation
    Visita mi Blog en Geeks.ms
    Sigueme en Twitter
    martes, 1 de febrero de 2011 18:06
    Moderador
  • Hola Josue, también probé con el botón </> de la barra del editor (comentado en un post "¿Qué podemos hacer para mejorar los foros?" - Sugerencia. Mejorar la edición de un comentario") y tampoco funcionó, supongo que por el mismo tema que tú comentas: "Pues IE9 tiene un problema con los editores de texto, no suele funcionar bien".

    Llevo comentando esto hace tiempo, incluso el tema de que no me avisa por e-mail cuando hay una respuesta. He vuelto a introducir mi correo en mi configuración, a ver si ahora funciona. Y a mi me parece que comentar algo con carácter constructivo, aportando ideas como el otro post que mencionaba, no es una cuestión de "romper la baraja". Siempre intentamos mejorar las cosas por el bien de la Comunidad.

    CorsarioVasco, gracias por tu trabajo de edición. Aunque no termina de convencerme esta combinación (este lío) del ThreadPool, con el BackgroundWorker y con el Dispatcher.Invoke(), por el simple hecho de querer asignar un control Image desde otro hilo. Y es una verdadera lástima, ya que con sólo el BackgroundWorker se puede asignar cualquier elemento de la UI-WPF, excepto claro, un control Image. He reconstruido el código y me he encontrado con una desvantaja más: no se puede controlar el progreso del cálculo mediante el Worker.Report(porcentaje) desde la clase donde se realiza el cálculo, ya que lanza el error:  "En esta operación ya se ha llamado a OperationCompleted y no se permiten más llamadas". En fin, voy a hacer más pruebas a ver si lo arreglo (aunque lo dudo, ya está bastante liado el código).

    Gracias y salu2.

     

    miércoles, 2 de febrero de 2011 7:48