I am using AsyncTask to run a background operation. Of course switching to another thread while already working in a background thread does not make a lot of sense in general, except the other thread is the UI thread. This what I would like to to: While the task is running I need to "access" the UI, e.g. to show a dialog to ask the user how to proceed.
How can this be done? I thought I could use Runnable
with myActivity.runOnUiThread(runnable)
but this does not work:
private void copyFiles() {
CopyTask copyTask = new CopyTask(this);
copyTask.execute();
}
// CustomAsyncTask is a AsyncTask subclass that takes a reference to the current
// activity as parameter
private class CopyTask extends CustomAsyncTask<Void, Void, Void> {
private doCopy;
@Override
protected Boolean doInBackground(Void... params) {
// Custom code, e.g. copy files from A to B and check for conflict
for (File file : allFiles) {
doCopy = true;
if (isConflict(file)) {
// Stop current thread and ask for user feedback on UI Thread
Runnable uiRunnable = new Runnable() {
public void run() {
// Pos 1. --> Execute custom code, e.g. use AlertDialog to ask user if file should be replaced...
doCopy = false;
synchronized (this) {
this.notify();
}
}
});
synchronized(uiRunnable) {
// Execute code on UI thread
activity.runOnUiThread(uiRunnable);
// Wait until runnable finished
try {
uiRunnable.wait();
}
catch (InterruptedException e ) {
e.printStackTrace();
}
}
}
// Pos 2. --> Continue work
if (doCopy)
copyFromAToB(File);
}
return null;
}
}
Within doInBackground()
(--> in a background thread) the AsyncTask
calls activity.runOnUiThread(uiRunnable)
. Next uiRunnable.wait()
is called. Regarding to the docu wait()
should do the following:
Causes the calling thread to wait until another thread calls the notify() or notifyAll() method of this object.
Thus the background thread should wait to continue its work until this.notify()
(== uiRunnable.notifiy()
) is called on another thread (= the UI thread), shouldn't it?
Well, id does not wait! After calling uiRunnable.wait()
the background thread immediately continues by jumping to if (doCopy)...
. It seems that the background thread and the main thread are executed in parallel (not surprising since this is what thread do...) and thus its a race condition whether doCopy = false
on the UI thread or if (doCopy)
on the background thread is reached first.
How is this possible? Why doesn't wait()
works as described? Or am I getting something wrong?
Thank you very much!
EDIT: To avoid missunderstandings: Of course I know the lifecycle methodes of AsyncTask but as far as I understand them, they are not what I am looking for (see my reply to the comment blow).
Interrupting the AsyncTask as soon as a UI interaction is necessary, query the UI and start a new AsyncTask would be possible of course. However this would result in code which is very hard to read/understand/maintain.
As I understand the docu of wait()
everything should work fine here. Primary question is not how to do UI interaction during the lifecycle of an AsyncTask but why wait()
does not work as expected.
When you start an AsyncTask
first the onPreExecute()
method runs on the UI thread. You can override this method to make changes to the UI prior to the doInBackground()
method running.
After the doInBackground()
method finishes, the onPostExecute()
method runs on the UI thread, so you can use this to make changes to the UI from here. If you need to make regular changes to the UI Thread during the doInBackground()
method you override the onProgressUpdate()
method which runs on the UI Thread, and then call it from within doInBackground()
, which will allow you to periodically update the UI.
You could use something like the following;
private class DoStuffTask extends AsyncTask {
@Override
protected void doInBackground(Object... args) {
// Do stuff
onProgressUpdate(x);
// Do more stuff
}
@Override
protected void onProgressUpdate(Object... args) {
// Update your UI here
}
}
Now if this doesn't quite do it and you want the AsyncTask to wait for input during the doInBackground()
method it is probably worth considering using multiple AsyncTasks
instead. You can then finish each AsyncTask
, ask for input, and then start a new AsyncTask
to continue working.
Given that AlertDialog
instances are asynchronous, this is probably the preferred solution because you can start the next AsyncTask
from the AlertDialog
itself.
If you would prefer to use a single AsyncTask
you can use wait from within your AsyncTask
to prevent execution continuing until some condition is met. Instead of using a new Runnable
we are just using two threads in this instance, the thread running doInBackground()
and the main thread, and we are synchronizing on the AsycTask
itself.
Example below;
public class TestTask extends AsyncTask{
private boolean notified;
private Promptable p;
public interface Promptable { public abstract void prompt(); }
public TestTask(Promptable p){
this.p = p;
}
@Override
protected Object doInBackground(Object... arg0) {
Log.d("First", "First");
onProgressUpdate(null);
synchronized(this){
while(!notified){
try{
this.wait();
}
catch(InterruptedException e){ }
}
}
Log.d("Second", "Second");
return null;
}
@Override
protected void onProgressUpdate(Object... args){
synchronized(this){
notified = true;
p.prompt();
this.notify();
}
}
}
In the example above, assume that your Activity
is parsed into the AsyncTask
's constructor, and that it implements an interface we create called Promptable
. You'll notice that even though we're calling wait()
we are putting it in a while loop. If we didn't do this, and somehow notify()
got called before wait()
then your thread would lock up indefinitely. Also, you can't depend on the fact that your thread will wait forever, so the while loop ensures that it doesn't continue until notify is called.
I hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With