10

Is it possible to make AsyncTask.doInBackground synchronized - or achieve the same result in another way?

class SynchronizedTask extends AsyncTask {

    @Override
    protected synchronized Integer doInBackground(Object... params) {
        // do something that needs to be completed 
        // before another doInBackground can be called
    }
}

In my case, any AsyncTask.execute() can be started before a previous one has completed, but I need to execute the code in doInBackground only after the previous task has finished.

EDIT: As correctly pointed out, the synchronization works only on the same object instance. Unfortunately, it is not possible to create an AsyncTask and call execute() more than once on the same object instance, as specified in the "Threading rules" section of the AsyncTask documentation.

The solution is to use a custom Executor to serialize the tasks, or, if you use API 11 or above, AsyncTask.executeOnExecutor(), as suggested in the comments below.

I posted an answer showing an implementation of a SerialExecutor that can be used to queue tasks that will be executed sequentially.

Community
  • 1
  • 1
Lorenzo Polidori
  • 10,332
  • 10
  • 51
  • 60
  • 1
    That's not what synchronized means! It means that the function (or the block) is executed without interleaving with another thread on this object. – Bondax Mar 27 '12 at 16:59
  • @Bondax Yes, I know. But `doInBackground` can be called by different background threads, as per documentation. Since the UI thread can call `new SynchronizedTask()` at any time before a previous one has finished, you can have two or more different interleaving threads executing `doInBackground` at the same time. – Lorenzo Polidori Mar 27 '12 at 17:11
  • 1
    Yes, but on different AsyncTask instances! – Bondax Mar 27 '12 at 17:18
  • 1
    If you use API level 11, you can create [SingleThreadExecutor](http://developer.android.com/reference/java/util/concurrent/Executors.html) and start you AsyncTask by calling [AsyncTask.executeOnExecutor()](http://developer.android.com/reference/android/os/AsyncTask.html), which resulting a queuing AsyncTask execution. – yorkw Mar 27 '12 at 22:56
  • @yorkw Many thanks for the input, this is exactly what I need. However, I am targeting APIs below level 11, so I think I need to implement myself a custom `Executor` which will be used by each Activity to serialize the task execution. – Lorenzo Polidori Mar 28 '12 at 10:19
  • @LorenzoPolidori, Check out my answer [here](http://stackoverflow.com/questions/7211684/asynctask-executeonexecutor-before-api-level-11) too see how to port executeOnExecutor() under API Level 11. In addition, worth to check out [this Q&A](http://stackoverflow.com/questions/7911333/queing-multiple-async-tasks-in-android-1-6/7911399#7911399) as well. – yorkw Mar 28 '12 at 10:25
  • @Bondax If the `doInBackgournd()` method in different `AysncTask` instance objects read and write some same variables. This could make error. So I think to synchronize the AysncTask instance object is necessary. – Alston Aug 06 '14 at 06:57
  • @yorkw If the target devices' OS is `Froyo`, can it support `SingleThreadExecutor`? – Alston Aug 06 '14 at 06:59
  • 1
    @Stallman, yes, this API is there since API Level 1, check out [Excutors.newSingleThreadExecutor()](http://developer.android.com/reference/java/util/concurrent/Executors.html) – yorkw Aug 06 '14 at 07:56
  • @yorkw Thanks to the second question. What I mean in the first question is that I only want `one thread` running the task(extends `AysncTask`) and that's what it currently does. But for older version API and OS, the `asyncTask` run on concurrently.(http://commonsware.com/blog/2012/04/20/asynctask-threading-regression-confirmed.html) Should I add `synchronize` or manage the execution of the thread or just `do something` to prevent the callers call the task concurrently? Thanks. – Alston Aug 06 '14 at 08:19
  • 1
    @Stallman: The async task should only be executed by one thread at all. Don't call doInBackground() manually. Only global variables can be accessed in a critical way, so make sure the access to these variables is thread safe. 'Synchronized' means: No two threads can execute that method on that object instance. Hence and again: dont call doInBackground() manually! – Bondax Aug 06 '14 at 15:44

4 Answers4

20

Ideally, I'd like to be able to use AsyncTask.executeOnExecutor() with a SERIAL_EXECUTOR, but this is only available for API level 11 or above:

new AsyncTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, params);

To target the Android APIs below level 11, I ended up implementing a custom class which encapsulates an ExecutorService with a thread pool size of 1. The full code is open-sourced here.

Executors.newFixedThreadPool(int nThreads) creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At any point, at most nThreads threads will be active processing tasks. In my case, nThreads is 1, which means tasks can be queued, but only one task will be executed at any given time.

Here is the code:

public abstract class SerialExecutor {
    private final ExecutorService mExecutorService;

    public SerialExecutor() {
        mExecutorService = Executors.newFixedThreadPool(1);
    }

    public void queue(Context context, TaskParams params) {
        mExecutorService.submit(new SerialTask(context, params));
    }

    public void stop() {
        mExecutorService.shutdown();
    }

    public abstract void execute(TaskParams params);

    public static abstract class TaskParams { }

    private class SerialTask implements Runnable {
        private final Context mContext;
        private final TaskParams mParams;

        public SerialTask(Context context, TaskParams params) {
            mContext = context;
            mParams = params;
        }

        public void run() {
            execute(mParams);
            Activity a = (Activity) mContext;
            a.runOnUiThread(new OnPostExecute());
        }
    }

    /**
     * Used to notify the UI thread
     */
    private class OnPostExecute implements Runnable {

        public void run() {

        }
    }
}

This can be extended and used as a serial task executor in an Activity:

public class MyActivity extends Activity {
    private MySerialExecutor mSerialExecutor;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...
        mSerialExecutor = new MySerialExecutor();
    }

    @Override
    protected void onDestroy() {
        if (mSerialExecutor != null) {
            mSerialExecutor.stop();
        }
        super.onDestroy();
    }

    public void onTrigger(int param) {
        mSerialExecutor.queue(this, new MySerialExecutor.MyParams(param));
    }

    private static class MySerialExecutor extends SerialExecutor {

        public MySerialExecutor() {
            super();
        }

        @Override
        public void execute(TaskParams params) {
            MyParams myParams = (MyParams) params;
            // do something...
        }

        public static class MyParams extends TaskParams {
            // ... params definition

            public MyParams(int param) {
                // ... params init
            }
        }
    }
}
Lorenzo Polidori
  • 10,332
  • 10
  • 51
  • 60
5

You may want to think about using IntentService instead. It seems like it may be a better fit for your process since it has built in features for queuing.

shibbybird
  • 1,245
  • 13
  • 28
  • Thanks, +1 for the good alternative, but I need to have a serial task execution which is carried out independently by each resumed Activity. This is why I need a per-Activity background task serializer similar to `AsyncTask` rather than a central service to be shared among all the Activities. – Lorenzo Polidori Mar 28 '12 at 10:16
  • I think what will give you similar results to executeOnExecuter as you just noted in this question is calling a Static Syncronized Method in a class from the AsyncTask(this will lock the class out from other threads) that contains your work to process. This however along with executeOnExecutor will not insure the order of your threads being processed. If you feel that you are comfortable with the way in which you are threading with API 11's executeOnExecutor then use this process if not you should queue your threads. – shibbybird Mar 28 '12 at 13:22
  • To make sure the order of the tasks is preserved, I have implemented a `SerialExecutor` class which uses an `ExecutorService` created by `Executors#newFixedThreadPool(1)` (see my answer to this question). From the documentation of `Executors#newFixedThreadPool(int nThreads)`, this method creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At any point, at most `nThreads` threads will be active processing tasks. If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available. – Lorenzo Polidori Mar 28 '12 at 14:04
  • Your answer looks like it should definitely work. Sorry I just woke up when I wrote that. Also to better answer your comment. You could just bind the IntentService to any given Activity. That should also work. – shibbybird Mar 28 '12 at 14:23
0
public class RestAsyncTask1 extends AsyncTask<String, Void, String> {

    private AsyncTaskCompleteListener callback;
    private Context context;
    private String method;
    private static final AtomicInteger PROGRESS_NUM = new AtomicInteger(0);
    private static ProgressDialog PROGRESS_DIALOG;

    public RestAsyncTask1(Context context, AsyncTaskCompleteListener callback, String method) {
        this.callback = callback;
        this.context = context;
        this.method = method;
    }

    public static String format(String url, String... params) {
        String[] encoded = new String[params.length];

        for (int i = 0; i < params.length; i++) {
            encoded[i] = Uri.encode(params[i]);
        }

        return String.format(url, (String[]) encoded);
    }

    @Override
    protected void onPreExecute() {
        int x = PROGRESS_NUM.getAndIncrement();

        if (x == 0) {
            String title = "M_yug";
            PROGRESS_DIALOG = new ProgressDialog(context);
           // PROGRESS_DIALOG.setTitle(title);
            PROGRESS_DIALOG.setIndeterminate(true);
            PROGRESS_DIALOG.setCancelable(false);
            PROGRESS_DIALOG.setOnCancelListener(null);
            PROGRESS_DIALOG.setMessage("Loading. Please wait...");
            PROGRESS_DIALOG.show();
        }
    }

    @Override
    protected String doInBackground(String... params) {
        String url = params[0];
        String response = null;
        HttpURLConnection connection = null;

        if (params.length > 1) {
            if (method.equals(Method.GET)) {
                url = format(url, (String[]) Arrays.copyOfRange(params, 1, params.length));
            } else if (params.length > 2) {
                url = format(url, (String[]) Arrays.copyOfRange(params, 1, params.length - 1));
            }

            try {
                URL call = new URL(url);
                connection = (HttpURLConnection) call.openConnection();
                connection.setRequestProperty("Content-Type", "application/json");
                //connection.setRequestProperty("M-Yug", Utilities.VERSION);
                connection.setRequestMethod(method);
                connection.setDoOutput(true);

                if (method.equals("POST")) {
                    BufferedOutputStream outputStream = new BufferedOutputStream(connection.getOutputStream());
                    outputStream.write(params[params.length - 1].getBytes());
                    outputStream.flush();
                }

                int status = connection.getResponseCode();

                if (status == HttpURLConnection.HTTP_OK) {
                    InputStream is = connection.getInputStream();
                    response = readValue(is);
                } else if (status == 400) {
                    InputStream is = connection.getErrorStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                    StringBuilder builder = new StringBuilder();
                    String line;

                    while ((line = reader.readLine()) != null) {
                        builder.append(line);
                    }

                    reader.close();
                    Toast.makeText(context, "" + builder.toString(), Toast.LENGTH_SHORT).show();
                }

                connection.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }

        return response;
    }

    @Override
    protected void onPostExecute(String s) {
        int x = PROGRESS_NUM.decrementAndGet();

        if (x == 0 && PROGRESS_DIALOG != null && PROGRESS_DIALOG.isShowing()) {
            PROGRESS_DIALOG.dismiss();
        }

        if (s!=null) {
            String resopnse=s.toString();
            callback.onSuccess(resopnse);
        } else {
           Toast.makeText(context,"Server Not Responding",Toast.LENGTH_SHORT).show();
        }
    }

    private String readValue(InputStream is) {
        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();
        String line;

        try {
            br = new BufferedReader(new InputStreamReader(is));

            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
        }

        return sb.toString();
    }

    enum Method {
        GET, POST
    }
}
krlzlx
  • 5,752
  • 14
  • 47
  • 55
-2

AsyncTask is used to run a background thread so that you current process is not interupted .

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
 protected Long doInBackground(URL... urls) {
     int count = urls.length;
     long totalSize = 0;
     for (int i = 0; i < count; i++) {
         totalSize += Downloader.downloadFile(urls[i]);
         publishProgress((int) ((i / (float) count) * 100));
     }
     return totalSize;
 }

 protected void onProgressUpdate(Integer... progress) {
     setProgressPercent(progress[0]);
 }

 protected void onPostExecute(Long result) {
     showDialog("Downloaded " + result + " bytes");
 }

}

where first of all your doInBackground function iscalled and the returned object will move to on post execute. which line of code you want to run after some process you can put that in PostExecute function. this will surely help you

code post
  • 81
  • 5