j2me сеть, потоки и тупики - PullRequest
7 голосов
/ 15 мая 2009

Простой фрагмент кода мидлета (класс Moo) ниже (после выдержек) взаимоблокируется (по крайней мере, я предполагаю, что он заблокирован после прочтения этого поста в темах здесь ).

Я воспроизвел соответствующие выдержки из поста:


    String url = ...
    Connection conn = null;

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

Корень проблемы заключается в блокирующем характере вызова open (). На некоторых платформах система выполняет фактическое соединение под крышками, эквивалентно отдельному потоку. Вызывающий поток блокируется, пока поток соединения не установит соединение. В то же время подсистема безопасности может потребовать, чтобы пользователь подтвердил соединение, и поток соединений блокируется, пока поток событий не получит подтверждение от пользователя. Произошла взаимоблокировка, поскольку поток событий уже ожидает поток подключения.


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

    }

}

Может кто-нибудь сказать мне, как здесь осуществляется вызов системного потока (потоки событий и уведомлений) и последовательность событий, ведущих к тупику. Мне не ясно, какие нити здесь приводят к тупику.

  1. Есть ли документация по модели потоков j2me?
  2. Где я могу получить исходные коды для системных классов j2me (я хочу проверить реализацию классов Connection)?

РЕДАКТИРОВАТЬ : В приведенном выше коде я получаю логику. Но приведенный ниже код должен по крайней мере работать правильно? Этот также блокирует, где я делаю сетевое соединение в отдельном потоке.

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

    }
}

Ответы [ 4 ]

9 голосов
/ 15 мая 2009

Где взять источники Системные классы j2me (хочу проверить из реализации подключения классы)?

Ты не можешь. Это на самом деле зависит от производителя. То, как Nokia справляется с этой ситуацией, может отличаться от Motorola.

Урок, который вы должны усвоить, состоит в том, что не выполняйте дорогостоящие вычисления в системных обратных вызовах, поскольку это может сделать систему не отвечающей на запросы. Поэтому поместите трудоемкую операцию в отдельный поток и всегда возвращайтесь из обратных вызовов как можно раньше.

Даже если вы создали отдельный поток во втором примере, вы ожидаете его завершения в paint (), и в итоге он вообще не приводит к поточности!

Одна вещь, которую вы можете сделать, это

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();
    }
}
1 голос
/ 15 мая 2009

Canvas.paint () - это метод доставки событий , что означает, что он вызывается потоком системных событий.

Предположим, что в системе реализован как вызов Canvas.paint (), так и обработка события подтверждения пользователя, выполняемая потоком событий пользовательского интерфейса (UT).

Теперь, когда UT заблокирован внутри Canvas.paint () с помощью Connector.open (), UT наверняка не сможет обработать следующее наступающее событие, которое в этом случае является событием подтверждения пользователя, вызванным Connector.open (). UT не может обработать другое событие, если оно заблокировано внутри кода вашего приложения.

Вот почему возникает тупик здесь , поток соединения ожидает, что что-то никогда не произойдет, и блокирует UT навсегда.

В общем, вы не должны ожидать, как будет реализован поток системных событий, и пытаться как можно быстрее вернуться из метода обработки событий. В противном случае вы можете получить более низкую производительность или тупик, как этот.

1 голос
/ 15 мая 2009

Ну, основная проблема в том, что некоторые реализации Java VM используют один и тот же поток Java для всего.

Первое, что вам нужно выяснить в модели потоков вашей виртуальной машины, это кто ее разработал.

Здесь находится список лицензиатов J2ME: http://java.sun.com/javame/licensees/index.jsp

Из этой информации попытайтесь выяснить, сколько собственных потоков использует ваша ВМ. 2 обычные модели либо запускают всю интерпретацию байт-кода в одном собственном потоке, либо запускают каждый поток Java в своем собственном потоке.

Следующим шагом является сбор информации о том, насколько асинхронными являются API-интерфейсы операционной системы. При разработке виртуальной машины лицензиатам пришлось бы писать собственный код для переноса виртуальной машины в операционную систему. Любое межпроцессное взаимодействие или использование медленной среды передачи (от флэш-карты до сигнала GPS) может быть реализовано с использованием отдельного собственного потока, который может позволить потоку интерпретатора байт-кода продолжать работу, пока система ожидает некоторых данных.

Следующий шаг - понять, насколько плохо реализована виртуальная машина. Как правило, ваша проблема возникает, когда виртуальная машина использует только один внутренний поток Java для всех методов обратного вызова в спецификации MIDP. Таким образом, у вас не будет возможности реагировать на события клавиатуры до тех пор, пока ваше соединение не будет открыто, если вы попытаетесь открыть его в неправильном потоке Java.

Хуже того, вы можете фактически предотвратить обновление экрана, потому что Canvas.paint () будет вызываться в том же потоке java, что и javax.microedition.media.PlayerListener.playerUpdate (), например.

С точки зрения реализации виртуальной машины золотое правило состоит в том, что любой обратный вызов, который вы не контролируете (потому что он может оказаться в «пользовательском» коде, например, слушатель), не может быть вызван из того же потока Java, который вы используете стандарт разблокировки Вызовы API. Многие виртуальные машины просто нарушают это правило, поэтому Sun рекомендует разработчикам JavaME обходить его.

0 голосов
/ 20 июня 2009

Некоторые хорошие идеи, но похоже, что в примере с Маноджем есть условие гонки.

Возможно несколько вызовов рисования во время загрузки изображения, что приводит к созданию нескольких потоков, которые все загружают одно и то же изображение (Один из примеров дополнительных вызовов рисования - это когда всплывает приглашение HTTP-соединения).

Поскольку все вызовы рисования выполняются в одном потоке, мы можем избежать синхронизации, протестировав и установив флаг внутри вызова рисования. Ниже приведена попытка улучшения версии:

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

Обратите внимание на использование ключевого слова final , чтобы избежать пустого теста и явного закрытия потока, возвращаемого openInputStream .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...