16

I have AsyncTask that processes some background HTTP stuff. AsyncTask runs on schedule (Alarms/service) and sometime user executes it manually.

I process records from SQLite and I noticed double-posts on server which tells me that sometime scheduled task runs and at the same time user runs it manually causing same record to be read and processed from DB twice. I remove records after they processed but still get this.

How should I handle it ? Maybe organize some kind of queing?

katit
  • 17,375
  • 35
  • 128
  • 256

7 Answers7

17

You can execute your AsyncTask's on an Executor using executeOnExecutor()

To make sure that the threads are running in a serial fashion please use: SERIAL_EXECUTOR.

Misc: How to use an Executor

If several activities are accessing your DB why don't create a sort of gateway database helper and use the synchronized block to ensure only one thread has access to it at an instant

Community
  • 1
  • 1
Reno
  • 33,594
  • 11
  • 89
  • 102
11

Or, you can try this to see if the Task is currently running or not:

if (katitsAsyncTask.getStatus().equals(AsyncTask.Status.FINISHED))
     katitsAsyncTask.execute();
else
     // wait until it's done.
BonanzaDriver
  • 6,411
  • 5
  • 32
  • 35
  • Won't work. I don't keep reference to my AsyncTask and they called from different Activities and from Service – katit Jul 11 '11 at 03:03
  • Actually, I run mine in my Services also. My Service keeps the reference and won't start a new one until the existing one is finished. I simply keep a queue of Activities that wish to use it and run them serially through the list, but only a single invocation at a time ... it does work. – BonanzaDriver Jul 11 '11 at 03:12
  • My service doesn't run all the time, I execute it using Alarms. I can use "hard" flags in Database but I afraid that if AsyncTask dies for any reason and won't update this flag - another one will never run. I can do timestamp and declare AsyncTask "dead" if it's running for certain period of time but I don't like this solution – katit Jul 11 '11 at 03:20
  • As these are background web services I'd recommend using the "non-sticky" Services because of that fact. If you're using proper error handling and feel concerned that a service might "die on the vine" then use a technqiue of tracking which records are being submitted, or allow a 2nd instance if XXX time has passed since the first one started, or ..... only your imagination is limiting you here. – BonanzaDriver Jul 11 '11 at 03:39
  • What is non-sticky? I use AlarmManager to start my service on predefined interval. I though this approach is best for battery life and it's easier to deal with. I do handle errors but you never know if user shuts down phone or whatever happen and you have "Running"=true recorded somewhere.. – katit Jul 11 '11 at 03:51
  • Do yourself a favor and read up on the API ... I think you'll find some surprising answers. – BonanzaDriver Jul 11 '11 at 04:47
  • @katit How did you implement it in the end? – powder366 Aug 04 '13 at 12:05
6

Initialize the AsyncTask to null. Only create a new one if it is null. In onPostExecute, set it to null again, at the end. Do the same in onCancelled, in case the user cancels this. Here's some untested code to illustrate the basic idea.

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class FooActivity extends Activity {

    private class MyAsyncTask extends AsyncTask<Foo, Foo, Foo> {
        @Override
        protected void onPostExecute(Foo foo) {
                    // do stuff
            mMyAsyncTask = null;
        }

        @Override
        protected void onCancelled() {
            // TODO Auto-generated method stub
            mMyAsyncTask = null;
        }

        @Override
        protected Foo doInBackground(Foo... params) {
                    try {
                         // dangerous stuff                         
                    } catch (Exception e) {
                        // handle. Now we know we'll hit onPostExecute()
                    }

            return null;
        }
    }

    private MyAsyncTask mMyAsyncTask = null;

    @Override
    public void onCreate(Bundle bundle) {
        Button button = (Button) findViewById(R.id.b2);
        button.setOnClickListener(new View.OnClickListener() {

            public void onClick(View v) {
                if (mMyAsyncTask == null) {
                    mMyAsyncTask = new MyAsyncTask();
                    mMyAsyncTask.execute(null);
                }
            }

        });
    }

}

Thomas Dignan
  • 7,052
  • 3
  • 40
  • 48
  • I tried every other solution but was still getting db not open exception while trying to access db inside an AsyncTask and this is what worked for me. Thanks – Sayed Jalil Hassan Aug 30 '13 at 09:28
1

I know this was a while ago now, and you have solved your problem. but I just had a similar problem. Reno's suggestion put me on the right track, but for those who have been finding it difficult to fill in the gaps. Here is how I overcame a similar issue to that of katit's.

I wanted a particular AsyncTask to only run if it was not currently running. And as a forward from Reno's suggestion, the AsyncTask interface has been created to handle all the nitty gritty processes in properly dealing with threads for Android. Which means, the Executor is built in. As this blog suggests:

"When execute(Object.. params) is invoked on an AsyncTask the task is executed in a background thread. Depending on the platform AsyncTasks may be executed serially (pre 1.6 and potentially again in 4+), or concurrently (1.6-3.2).

To be sure of running serially or concurrently as you require, from API Level 11 onwards you can use the executeOnExecutor(Executor executor, Object.. params) method instead, and supply an executor. The platform provides two executors for convenience, accessable as AsyncTask.SERIAL_EXECUTOR and AsyncTask.THREAD_POOL_EXECUTOR respectively. "

So with this in mind, you can do thread blocking via the AsyncTask interface, it also implies you can simply use the AsyncTasks.getStatus() to handle thread blocking, as DeeV suggests on this post.

In my code, I managed this by:

Creating a global variable defined as:

private static AsyncTask<String, Integer, String> mTask = null;

And in onCreate, initialising it as an instance of my AsyncTask called CalculateSpecAndDraw:

mTask = new CalculateAndDrawSpec();

Now when ever I wish to call this AsyncTask I surround the execute with the following:

if(mTask.getStatus() == AsyncTask.Status.FINISHED){
             // My AsyncTask is done and onPostExecute was called
            mTask = new CalculateAndDrawSpec().execute(Integer.toString(progress));
        }else if(mTask.getStatus() == AsyncTask.Status.PENDING){
            mTask.execute(Integer.toString(progress));
        }else{
            Toast.makeText(PlaySpecActivity.this, "Please Wait..", 1000).show();

        }

This spawns a new thread if it is finished, or if the thread state is PENDING, the thread is defined but has not been started we start it. But otherwise if the thread is running we don't re-run it, we simply inform the user that it is not finished, or perform what ever action we wish. Then if you wanted to schedule the next event rather than just block it from re running, take a look at this documentation on using executors.

Community
  • 1
  • 1
digiphd
  • 2,319
  • 3
  • 23
  • 22
1

How about just wrapping your check-what-to-send and send-it logic in a synchronized method? This approach seems to work for us.

hoffmanc
  • 614
  • 9
  • 16
0

try having some instance boolean value that gets set to "true" on the asynctask's preexecute then "false" on postexecute. Then maybe in doinbackground check if that boolean is true. if so, then call cancel on that particular duplicate task.

Kevin Qiu
  • 1,616
  • 1
  • 13
  • 15
  • 1
    I afraid that if I use flag in Application and my AsyncTask fails for some reason - than it will never execute again since flag already shows it started – katit Jul 11 '11 at 02:38
0

You could keep the state of the task in shared preferences. Check the value (Boolean perhaps) before starting the task. Set the state to finished(true?) in onPostExecute and false in onPreExecute or in the constructor

james
  • 26,141
  • 19
  • 95
  • 113