2009-05-15 14 views
7

La pieza simple de código midlet (clase Moo) a continuación (después de los extractos) interbloqueos (Al menos supongo que interbloqueos después de leer esta publicación en los hilos here).j2me red, subprocesos y bloqueos

He reproducido los extractos pertinentes de la publicación:

 

    String url = ... 
    Connection conn = null; 

    try { 
     conn = Connector.open(url); 
     // do something here 
    } 
    catch(IOException e){ 
     // error 
    } 
 

La raíz del problema es la naturaleza de bloqueo de la llamada open(). En algunas plataformas, el sistema realiza la conexión real bajo las cubiertas, en el equivalente de un hilo separado. El hilo de llamada bloquea hasta que el hilo de conexión realiza la conexión. Al mismo tiempo, el subsistema de seguridad puede requerir que el usuario confirme la conexión, y el hilo de conexión se bloquea hasta que el hilo del evento reciba la confirmación del usuario. El bloqueo se produce porque el hilo del evento ya está esperando el hilo de conexión.

 

public class Moo extends MIDlet { 

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException { 
     // TODO Auto-generated method stub 

    } 

    protected void pauseApp() { 
    } 

    protected void startApp() throws MIDletStateChangeException { 
     Display display = Display.getDisplay(this); 
     MyCanvas myCanvas = new MyCanvas(); 
     display.setCurrent(myCanvas); 
     myCanvas.repaint(); 

    } 

    class MyCanvas extends Canvas { 

     protected void paint(Graphics graphics) { 
       try { 
         Image bgImage = Image.createImage(getWidth(), getHeight()); 

         HttpConnection httpConnection = (HttpConnection) Connector 
             .open("http://stackoverflow.com/content/img/so/logo.png"); 
         Image image = Image.createImage(httpConnection 
             .openInputStream()); 
         bgImage.getGraphics().drawImage(image, 0, 0, 0); 
         httpConnection.close(); 

         graphics.drawImage(bgImage, 0, 0, 0); 
       } catch (IOException e) { 
         e.printStackTrace(); 
       } 
     } 

    } 

} 
 

Puede alguien por favor dígame cómo la invocación de hilos del sistema se hace aquí (hilos de eventos y notificación) y la secuencia de eventos que conducen al estancamiento. No estoy seguro de cuáles son los hilos involucrados aquí que conducen a un punto muerto.

  1. ¿Hay alguna documentación sobre el modelo de rosca j2me?
  2. ¿Dónde puedo obtener las fuentes para las clases del sistema j2me (quiero verificar la implementación de las clases de conexión)?

EDIT: En el código anterior obtengo la lógica. Pero el siguiente código al menos debería funcionar ¿verdad? Este también bloquea las conexiones donde estoy haciendo la conexión de red en un hilo separado.

 

public class Foo extends MIDlet { 

    protected void destroyApp(boolean arg0) throws MIDletStateChangeException { 
     // TODO Auto-generated method stub 
    } 

    protected void pauseApp() { 
     // TODO Auto-generated method stub 
    } 

    protected void startApp() throws MIDletStateChangeException { 
     Display display = Display.getDisplay(this); 
     MyCanvas myCanvas = new MyCanvas(); 
     display.setCurrent(myCanvas); 
     myCanvas.repaint(); 
    } 

    class MyCanvas extends Canvas { 
     protected void paint(Graphics graphics) { 
      try { 
       Image bgImage = Image.createImage(getWidth(), getHeight()); 

       FetchImage fetchImage = new FetchImage(); 
       Thread thread = new Thread(fetchImage); 
       thread.start(); 

       thread.join(); 

       bgImage.getGraphics().drawImage(fetchImage.image, 0, 0, 0); 

       graphics.drawImage(bgImage, 0, 0, 0); 
      } catch (Exception e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 
    } 

    public class FetchImage implements Runnable { 
     public Image image; 

     public void run() { 
      HttpConnection httpConnection; 
      try { 
       httpConnection = (HttpConnection) Connector 
         .open("http://10.4.71.200/stage/images/front/car.png"); 
       image = Image.createImage(httpConnection.openInputStream()); 
       httpConnection.close(); 
      } catch (IOException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 

    } 
} 

Respuesta

9

¿Dónde puedo obtener las fuentes de clases del sistema J2ME (Quiero comprobar a cabo la ejecución de la conexión clases)?

Usted no puede. De hecho depende del proveedor. La forma en que Nokia maneja esta situación puede ser diferente de Motorola.

La lección que debe aprender es que no se realizan cálculos costosos en las devoluciones de llamada del sistema, ya que esto podría hacer que el sistema no responda. Así que ponga la operación que consume mucho tiempo en un hilo seperado y siempre regrese de las retrollamadas tan pronto como sea posible.

Aunque hayas creado una hebra separada en tu segundo ejemplo, esperas a que termine en paint() y finalmente se obtiene como resultado que no se enhebre.

Una cosa que puede hacer es

class MyCanvas extends Canvas { 

    Image image; 
    boolean imageFetchFailed; 

    protected void paint(Graphics g) { 
     if (image == null) { 
      fetchImage(); 
      g.drawString("Fetching...", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP) 

     } else if (imageFetchFailed) { 
      g.drawString("Failed to fetch image", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP) 
     } else { 
      g.drawImage(image, 0, 0, 0); 
     } 
    } 


    private void fetchImage() { 
     new Thread(new Runnable() { 
      public void run() { 
       HttpConnection httpConnection = null; 
       try { 
        httpConnection = (HttpConnection) Connector 
          .open("http://10.4.71.200/stage/images/front/car.png"); 
        image = Image.createImage(httpConnection.openInputStream()); 
       } catch (IOException e) { 
        e.printStackTrace(); 
        imageFetchFailed = true; 
       } 

       if (httpConnection != null) { 
        try { 
         httpConnection.close(); 
        } catch (IOException ignored) { 
        } 
       } 

       // Following will trigger a paint call 
       // and this time image wont be null and will get painted on screen 
       repaint();  
      } 
     }).start(); 
    } 
} 
+0

Excelente pregunta y respuesta, especialmente si realmente quieres algo de claridad con esta cosa de enhebrar. – Sydwell

1

Bueno, el problema básico es que algunas implementaciones de Java VM utilizan el mismo hilo de Java para hacer todo.

Una de las primeras cosas que necesita saber sobre el modelo de subprocesamiento de su máquina virtual es quién la desarrolló.

Hay una lista de licenciatarios J2ME aquí: http://java.sun.com/javame/licensees/index.jsp

De esa información, tratar de averiguar cuántos hilos nativa su VM está utilizando. Los 2 modelos habituales ejecutan todas las interpretaciones de bytecode en un subproceso nativo o ejecutan cada subproceso Java en su propio subproceso nativo.

El próximo paso es recopilar información acerca de la asincronía de las API del sistema operativo subyacente. Al desarrollar la VM, los licenciatarios tendrían que escribir código nativo para portar la VM al sistema operativo. Cualquier comunicación entre procesos o el uso de un medio de transmisión lento (desde una tarjeta de memoria flash a una señal de GPS) podría implementarse utilizando un hilo nativo separado que podría permitir que el hilo del intérprete de código de bytes siga funcionando mientras el sistema espera algunos datos.

El siguiente paso es darse cuenta de cuán mal implementado está el VM. Normalmente, su problema ocurre cuando la máquina virtual usa solo un hilo interno de java para todos los métodos de devolución de llamada en la especificación MIDP. Por lo tanto, no tienes la oportunidad de reaccionar a los eventos del teclado hasta que se abra tu conexión si intentas abrirla en el hilo Java incorrecto.

Peor aún, puede evitar que la pantalla se actualice porque Canvas.paint() se llamaría en el mismo hilo de java que javax.microedition.media.PlayerListener.playerUpdate() por ejemplo.

Desde la perspectiva de implementación de VM, la regla de oro es que cualquier devolución de llamada que no controle (porque puede terminar en código de "usuario", como un oyente) no se puede invocar desde el mismo hilo java que utiliza desbloquear llamadas API estándar. Una gran cantidad de VM simplemente viola esa regla, de ahí la recomendación de Sun de que los desarrolladores de JavaME lo solucionen.

+0

Gracias. Actualizó la pregunta en función de su respuesta. – Abhi

1

Canvas.paint() es un event delivery method, lo que significa que es invocado por un subproceso de evento del sistema.

Supongamos que en un sistema, tanto la invocación de Canvas.paint() como la gestión de eventos de confirmación de usuario se implementan para que se realice mediante un subproceso de evento UI (UT).

Ahora, cuando UT está bloqueado dentro de Canvas.paint() por Connector.open(), UT no podrá manejar el próximo evento que viene, que en este caso es el evento de confirmación de usuario desencadenado por Connector.open() . No es posible para UT procesar otro evento cuando está bloqueado dentro del código de su aplicación.

Es por eso que se produce el interbloqueo here, el hilo de conexión está esperando que algo no ocurra, y bloquea UT para siempre.

En general, no debe esperar cómo se implementará la secuencia de eventos del sistema, y ​​trate de regresar del método de manejo de eventos lo más rápido posible. De lo contrario, es posible que reciba un rendimiento más bajo o un punto muerto como este.

+0

Gracias. Creo que en cierto modo lo entiendo ahora. ¿Cómo reorganizarías el código para que funcione? La forma en que veo es hacer que el sistema llame al método de pintura después de que la imagen se extrae de la red. Hay una mejor manera de hacerlo? ¿Hay algún patrón asociado con el problema que estoy enfrentando y una solución estándar para él? Lo siento, soy un completo novato en materia de J2ME y UI. – Abhi

+0

Manoj acaba de mostrarle un buen ejemplo, observe que thread.join() todavía bloqueó UT dentro de Canvas.paint(). Solo intente regresar de las devoluciones de llamadas de eventos lo más rápido posible y permita que la secuencia de eventos procese el próximo evento. – tingyu

0

Algunas buenas ideas, pero parece que hay una condición de carrera en el ejemplo de Manoj.

Se pueden realizar varias llamadas de pintura mientras se descarga una imagen, lo que hace que se creen múltiples subprocesos y todas descarguen la misma imagen (Un ejemplo de llamadas de dibujo extra es cuando aparece el mensaje de conexión HTTP).

Como todas las llamadas de pintura se realizan en el mismo subproceso, podemos evitar la sincronización probando y configurando un indicador dentro de la llamada de pintura.A continuación se muestra un intento de una versión mejorada:

class MyCanvas extends Canvas { 

    Image image; 
    boolean imageDownloadStarted; 
    boolean imageFetchFailed; 

    protected void paint(Graphics g) { 
     g.fillRect(0, 0, g.getClipWidth(), g.getClipHeight()); 
     if (image == null) { 
      if (imageDownloadStarted) 
       return; 
      imageDownloadStarted = true; 
      fetchImage(); 
      g.drawString("Fetching...", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP); 

     } else if (imageFetchFailed) { 
      g.drawString("Failed to fetch image", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP); 
     } else { 
      g.drawImage(image, 0, 0, 0); 
     } 
    } 

    private void fetchImage() { 
     new Thread(new Runnable() { 

      public void run() { 
       try { 
        final HttpConnection httpConnection = (HttpConnection) Connector.open("http://stackoverflow.com/content/img/so/logo.png"); 
        try { 
         final InputStream stream = httpConnection.openInputStream(); 
         try { 
          image = Image.createImage(stream); 
         } finally { 
          stream.close(); 
         } 
        } finally { 
         httpConnection.close(); 
        } 
       } catch (IOException e) { 
        e.printStackTrace(); 
        imageFetchFailed = true; 
       } 

       repaint(); 
      } 
     }).start(); 
    } 
} 

Nota el uso de la última palabra clave para evitar un examen nulo y el cierre explícito de la corriente devuelta por openInputStream.