1

I got some questions regarding the use of threads, specially when you have to wait for a thread to be finished so you can perform other operations.

In my app, I use threads for operations such as http connections or when I read from or write to a RecordStore.

For example in the following class that I use to initialize my thread, I retrieve some customers from a webservice using the method called HttpQueryCustomers.

    public class thrLoadCustomers implements Runnable {

    private RMSCustomer mRMSCustomer;    
    private String mUrl;

    public thrLoadCustomers(RMSCustomer rmsCust, String url) {        
        mRMSCustomer = rmsCust;        
        mUrl = url;
    }

    public void run() {
        String jsonResultados = "";
        try {
            jsonResultados = HttpQueryCustomers();
        } catch (IOException ex) {
            //How to show a message from here??
        } catch (SecurityException se) {
             //How to show a message here??
        } catch (NullPointerException npe) {
             //How to show a message from here??
        }
        if (!jsonResultados.equals("")) {
            try {
                mRMSCustomer.save(jsonResultados);
            } catch (RecordStoreException ex) {
               //How to show a message from here???
            }
        }

    }

    public String HttpQueryCustomers() throws IOException,SecurityException,NullPointerException {
        StringBuffer stringBuffer = new StringBuffer();
        HttpConnection hc = null;
        InputStream is = null;
        System.out.println(mUrl);
        try {
            hc = (HttpConnection) Connector.open(mUrl);

            if (hc.getResponseCode() == HttpConnection.HTTP_OK) {
                is = hc.openInputStream();
                int ch;
                while ((ch = is.read()) != -1) {
                    stringBuffer.append((char) ch);
                }
            }

        } finally {
            is.close();
            hc.close();
        }
        String jsonData = stringBuffer.toString();
        return jsonData.toString();
    }
}

Notice in the above class that I pass a parameter called rmsCust of the type RMSCustomer

RMSCustomer is a class that I use to handle all the operations related to RMS:

public class RMSCustomer {

    private String mRecordStoreName;
    private Customer[] mCustomerList;

    public RMSCustomer(String recordStoreName) {
        mRecordStoreName = recordStoreName;
    }

    public Customer[] getCustomers() {
        return mCustomerList;
    }

    public Customer get(int index) {
        return mCustomerList[index];
    }  

    public void save(String data) throws RecordStoreException,JSONException,NullPointerException {
        RecordStore rs = null;
        int idNuevoRegistro;
        String stringJSON;
        try {
            rs = RecordStore.openRecordStore(mRecordStoreName, true);

            JSONArray js = new JSONArray(data); 
            //Set the size of the array
            mCustomerList = new Customer[js.length()];

            for (int i = 0; i < js.length(); i++) {

                JSONObject jsObj = js.getJSONObject(i);
                stringJSON = jsObj.toString();                   
                idNuevoRegistro = addRecord(stringJSON, rs); 
                //Add a new Customer to the array
                mCustomerList[i] = initializeCustomer(stringJSON, idNuevoRegistro);
            }


        } finally {
            if (rs != null) {
                rs.closeRecordStore();
            }
        }
    }

    public int addRecord(String stringJSON, RecordStore rs) throws JSONException,RecordStoreException {                    
        byte[] raw = stringJSON.getBytes();
        int idNuevoRegistro = rs.addRecord(raw, 0, raw.length);
        return idNuevoRegistro;             
    }   

    public Customer initializeCustomer(String stringJSON, int idRecord) throws JSONException {
        Customer c = new Customer();
        JSONObject jsonObj = new JSONObject(stringJSON);
        // Set Customer properties 
        //...
        return c;
    }


}

This class is used to show a list of customer and ,as you can see, it extends the List class and receives an array of Customers as a parameter.

public class ListCustomers extends List {

    private final Customer[] mData;

    public static ListCustomers create(Customer[] data) {
        int i = 0;
        for (; i < data.length; i++) {
            if (data[i] == null) {
                break;
            }
        }
        String[] names = new String[i];
        for (int j = 0; j < i; j++) {
            names[j] = data[j].name;
        }
        return new ListCustomers(names, data);
    }

    protected ListCustomers(String names[], Customer[] data) {
        super("List of Customer", IMPLICIT, names, null);
        mData = data;
    }
    public Customer getSelectedObject() {
        return mData[this.getSelectedIndex()];
    }
}

Finally this is how I call the thread from the MIDlet (using all the 3 previous classes) when I want to show a List of Customers:

private void showCustomerList(String url) {
        showWaitForm();
        if (scrCustomerList == null) {            
            rmsCustomers = new RMSCustomer("rmsCustomers");          

            thrLoadCustomers load = new thrLoadCustomers(rmsCustomers, url);
            Thread t = new Thread(load);
            t.start();
            try {
                t.join();
            } catch (InterruptedException ex) {

            }
            scrCustomerList = ListCustomers.create(rmsCustomers.getCustomers());
            scrCustomerList.addCommand(cmdSelect);
            scrCustomerList.addCommand(cmdBack);
            scrCustomerList.setCommandListener(this);
        }
        mDisplay.setCurrent(scrCustomerList);
    }

Now here's the problems I have :

  1. The showWaitForm() doesn't work (it sets a form with a Gauge as the Current form)
  2. I don't know how to show all the exceptions that might be thrown from within the thrLoadCustomers class.
  3. I don't know whether using t.join() is the best choice
  4. The last question is about something the book I'm reading says :

Threads, in particular, can be a scarce commodity. The MSA specification requires that an application must be allowed to create ten threads. Just because you can doesn’t mean you should. In general, try to use the fewest resources possible so that your application will run as smoothly as possible

This is the first time a use threads, and in my app I might have up to 10 threads (classes). However, I will only execute once thread at the time, will I be going against what the previous quotation says??

I hope I'm not asking too many questions. Thank you very much for your help.

P.D Much of the code I posted here wouldn't have been possible with the help of Gregor Ophey

Community
  • 1
  • 1
eddy
  • 4,373
  • 16
  • 60
  • 94

1 Answers1

2

Question #1 is about a different problem not related to threading, and for which very little code is shown. I'd suggest you to post a new dedicated question with proper explanation of the issue.

Questions #2 and #3: You could define a wrapper class like this:

    public class WSResult {
        private boolean success; //true if the WS call went ok, false otherwise
        private String errorMessage; //Error message to display if the WS call failed.
        private Object result; //Result, only if the WS  call succeeded.
        private boolean completed = false;

        //TODO getter and setters methods here
    }

In your screen, you can create an instance of result and wait for it:

    WSResult result = new WSResult();

    //Start thread here
    new Thread(new LoadCustomersTask(result)).start();

    //This is old school thread sync. 
    synchronized(result){
        while(!result.isCompleted()){
            result.wait();
        }
    }

    //Here the thread has returned, and we can diaplay the error message if any
    if(result.isSuccess()){

    } else {
        //Display result.getErrorMessage()
    }

Then your runnable would be like this:

    class LoadCustomersTask implements Runnable {
        private final WSResult result;
        public LoadCustomersTask(WSResult res){
            result = res;
        }

        public void run(){

            //Do the WS call

            //If it went well
            result.setSuccess(true);
            result.setResult(jsonResultados);

            //Else
            result.setSuccess(false);
            result.setErrorMessage("Your error message");


            //In any case, mark as completed
            result.setcompleted(true);

            //And notify awaiting threads           
            synchronized(result){
                result.notifyAll();
            }       
        }
    }

You can also do it with thread.join, but wait/notify is better because you not making the screen depend on the particular thread where the runnable runs. You can wait/notify on the result instance, as shown, or on the runnable if it is intended for a single use.

Question #4: Yes threads must not be abused, specially in JavaME where programs usually run in single core CPUs with a frecuency in the order of MHz. Try not to have more than 1-3 threads running at the same time. If you really need to, consider using a single thread for running all background tasks (a blocking queue).

Mister Smith
  • 27,417
  • 21
  • 110
  • 193
  • Please,could you explain to me these lines : `synchronized(result)` and `result.wait()`; – eddy Jun 26 '14 at 14:39
  • 1
    In my answer I'm using `Object.wait/Object.notify` instead of `Thread.sleep/Thread.interrupt` to coordinate the threads. You can read [here](http://stackoverflow.com/q/1036754) for more info about the differences, but the idea is the same: one thread waits until the second thread is done. The synchronized block is needed whenever you call wait/notify, otherwise an exception is thrown. – Mister Smith Jun 27 '14 at 12:21
  • Thank you very much @Mister Smith I did as you suggested and now I have a much more organized code. – eddy Jul 04 '14 at 12:21