2009-05-15 12 views
7

Prosty kawałek kodu pośredniego (klasa Moo) poniżej (po fragmentach) zakleszczenia (Przynajmniej zakładam, że zakleszczenia po przeczytaniu tego postu na wątki here).sieci j2me, wątki i zakleszczenia

Mam reprodukowane odpowiednich fragmentów ze stanowiska:

 

    String url = ... 
    Connection conn = null; 

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

Źródłem problemu jest natura blokowanie open() rozmowy. Na niektórych platformach system wykonuje rzeczywiste połączenie pod osłonami, w równoważnym oddzielnym wątku. Wątek wywołania blokuje się, dopóki wątek połączenia nie ustanowi połączenia. W tym samym czasie podsystem bezpieczeństwa może wymagać od użytkownika potwierdzenia połączenia, a wątki połączeń bloków do momentu, aż wątek zdarzenia zostanie potwierdzony przez użytkownika. Zakleszczenie występuje, ponieważ wątek zdarzenia już czeka na wątek połączenia.

 

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(); 
       } 
     } 

    } 

} 
 

Czy ktoś mógłby mi powiedzieć jak inwokacja wątek systemu odbywa się tutaj (zdarzeń i powiadamiania wątków) i sekwencję zdarzeń prowadzących do impasu. Nie mam jasności co do tego, o co chodzi w wątku, który prowadzi do impasu.

  1. Czy istnieje dokumentacja dotycząca modelu nici j2me?
  2. Skąd mogę pobrać źródła dla klas systemowych j2me (chcę sprawdzić implementację klas połączeń)?

EDIT: W powyższym kodzie otrzymuję logikę. Ale poniższy kod powinien przynajmniej działać poprawnie? Ten także zakleszcza się, gdy robię połączenie sieciowe w osobnym wątku.

 

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(); 
      } 
     } 

    } 
} 

Odpowiedz

9

Gdzie można uzyskać źródeł dla klas systemowych J2ME (Chcę sprawdzić na realizację połączenia klas)?

Nie możesz. To zależy od dostawcy. Sposób, w jaki Nokia radzi sobie z tą sytuacją, może być inny niż Motorola.

Lekcją, którą musisz się nauczyć, jest to, że nie wykonuje kosztownych obliczeń w oddzwanianiu systemowym, ponieważ może to spowodować brak reakcji systemu. Więc poświęć czasochłonną operację w osobnej wątku i zawsze wracaj z oddzwonień tak wcześnie, jak to możliwe.

Mimo że utworzyłeś osobny wątek w drugim przykładzie, możesz poczekać na jego zakończenie w farbie() i ostatecznie w ogóle nie powstaje żaden wątek!

Jedną rzeczą, jaką można zrobić, to

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

Doskonałe pytanie i odpowiedź, szczególnie jeśli naprawdę chcesz uzyskać pewną klarowność. – Sydwell

1

Cóż, podstawowym problemem jest to, że niektóre implementacje Java VM używać tego samego wątku Java, aby zrobić wszystko.

Jedną z pierwszych rzeczy, którą musisz się dowiedzieć o modelu gwintowania swojej maszyny wirtualnej, jest to, kto ją opracował.

Jest lista licencjobiorców J2ME tutaj: http://java.sun.com/javame/licensees/index.jsp

Od tej informacji, spróbuj dowiedzieć się, jak wielu rodzimych wątków Twój VM korzysta. Dwa zwykłe modele uruchamiają interpretację kodu bajtowego w jeden wątek natywny lub uruchamiają każdy wątek java w swój własny wątek.

Następnym krokiem jest zebranie informacji o tym, jak asynchroniczne są podstawowe interfejsy API systemu operacyjnego. Podczas opracowywania maszyny wirtualnej licencjobiorcy musieliby napisać natywny kod, aby przenieść maszynę wirtualną do systemu operacyjnego. Dowolna komunikacja między procesami lub użycie powolnego medium transmisyjnego (z karty flash do sygnału GPS) może być zaimplementowana przy użyciu osobnego wątku natywnego, który mógłby pozwolić wątkowi interpretera kodu bajtowego kontynuować działanie, podczas gdy system czeka na dane.

Następnym krokiem jest uświadomienie sobie, jak źle wdrożono maszynę wirtualną. Zazwyczaj problem występuje, gdy maszyna wirtualna używa tylko jednego wewnętrznego wątku java dla wszystkich metod wywołań zwrotnych w specyfikacji MIDP. Tak więc, nie masz szansy reagować na zdarzenia z klawiatury, dopóki połączenie nie zostanie otwarte, jeśli spróbujesz otworzyć go w niewłaściwym wątku java.

Co gorsza, można rzeczywiście zapobiec odświeżeniu ekranu, ponieważ funkcja Canvas.paint() byłaby wywoływana w tym samym wątku java, co na przykład javax.microedition.media.PlayerListener.playerUpdate().

Z punktu widzenia realizacji VM, złota zasada jest taka, że ​​każdy zwrotna, że ​​nie masz kontroli (ponieważ może skończyć się w „użytkownik” kodu, jak słuchacz) nie może być wywoływana z tego samego wątku java użyć Odblokuj standardowe wywołania API. Wiele maszyn wirtualnych po prostu łamie tę zasadę, stąd zalecenie Sun, którym zajmują się programiści JavaME.

+0

Dzięki. Zaktualizowano pytanie na podstawie Twojej odpowiedzi. – Abhi

1

Canvas.paint() to event delivery method, co oznacza, że ​​jest wywoływany przez wątek zdarzenia systemowego.

Założono w systemie, że zarówno wywoływanie Canvas.paint(), jak i obsługa zdarzeń potwierdzania przez użytkownika są realizowane przez wątek zdarzeń interfejsu użytkownika (UT).

Teraz, gdy UT jest zablokowany wewnątrz Canvas.paint() przez Connector.open(), UT z pewnością nie jest w stanie obsłużyć następnego nadchodzącego zdarzenia, które w tym przypadku jest zdarzeniem potwierdzającym użytkownika wywołanym przez Connector.open() . UT nie może przetworzyć innego zdarzenia, gdy jest zablokowane w kodzie aplikacji.

Dlatego występuje impas here, wątek połączenia czeka na coś, co nigdy się nie wydarzy i blokuje UT na zawsze.

Ogólnie rzecz biorąc, nie należy oczekiwać, w jaki sposób zostanie wdrożony wątek zdarzenia systemowego, i spróbować jak najszybciej powrócić z metody obsługi zdarzeń. W przeciwnym razie możesz otrzymać niższą wydajność lub zakleszczenie w ten sposób.

+0

Dzięki. Myślę, że teraz to rozumiem. Jak zmienić kod, aby działał? Widzę, że to system wywołuje metodę malowania po pobraniu obrazu z sieci. Czy istnieje lepszy sposób na zrobienie tego? Czy istnieje jakiś wzór związany z problemem, z którym się spotykam i standardowe rozwiązanie tego problemu? Przepraszam, jestem kompletnym nowicjuszem w sprawach J2ME i UI. – Abhi

+0

Manoj właśnie pokazał ci dobry przykład, zauważ, że thread.join() nadal blokował UT wewnątrz Canvas.paint(). Po prostu spróbuj wrócić z wywołań zdarzeń tak szybko, jak to możliwe i niech wątek zdarzenia przetworzy następne zdarzenie. – tingyu

0

kilka dobrych pomysłów, ale wydaje się, że istnieje sytuacja wyścigu w przykładzie Manoj użytkownika.

Wiele wywołań farb jest możliwe podczas pobierania obrazu, co powoduje utworzenie wielu wątków, wszystkie pobierają ten sam obraz (Jednym z przykładów dodatkowych wywołań farb jest wyświetlenie monitu o połączenie HTTP).

Ponieważ wszystkie wywołania farb są wykonywane na tym samym wątku, możemy uniknąć synchronizacji, testując i ustawiając flagę wewnątrz wywołania farby.Poniżej jest próbą ulepszonej wersji:

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(); 
    } 
} 

Uwaga użycie końcowego słowem kluczowym uniknąć test zerowy i wyraźne zamknięcie strumienia zwracanego przez openInputStream.