2013-01-16 13 views
7

Sto riscontrando un problema con AsyncTask e onPostExecute. Sto scoprendo che onPostExecute è in esecuzione su un thread diverso rispetto al thread ui principale, il che sta causando un'eccezione CalledFromWrongThreadException quando si modifica qualsiasi vista.Android AsyncTask onPostExecute off del thread ui principale

Inserisco alcune registrazioni per vedere quali thread suPreExecute, doInBackground e onPostExecute sono in esecuzione. Vorrei vedere un risultato come questo ...

onPreExecute ThreadId: 1 
doInBackground ThreadId: 25 
onPostExecute ThreadId: 18 

Credo che il thread dell'interfaccia utente principale id è 1 e mi aspetto sia onPre e onpost sia per eseguire sul filo 1. sto facendo in modo di creare e anche chiamare il metodo execute dal thread ui (ad esempio in onCreate di un'attività).

Un'altra cosa da notare che ho notato è che in seguito le attività asincrone eseguiranno il loro metodo onPostExecute sullo stesso thread dei precedenti task asincroni sui metodi PostExecute (in questo caso il thread 18).

In questo momento, per aggirare questo problema, ho inserito il codice nei miei metodi onPostExecute in una chiamata a runOnUiThread, ma penso che questo sia hacky e vorrei ottenere il vero problema.

Sono fuori di idee! Qualcuno ha qualche intuizione? Sono felice di rispondere a qualsiasi domanda che potrebbe aiutarmi con ulteriori indagini!

EDIT:

Ci sono due modi che asincrone compiti sono in esecuzione nel codice. Mi chiedo se il secondo in questi esempi sta causando qualcosa di strano che accada?

public class SomeActivity extends Activity { 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main_layout); 

     new SomeAsyncTask().execute(); 
    } 

    private class SomeAsyncTask extends AsyncTask<String, Void, Integer> { 
     @Override 
     public void onPreExecute() { 
      Thread.currentThread().getId() // 1 
      //Show a dialog 
     } 

     @Override 
     public Integer doInBackground(String... params) { 
      Thread.currentThread().getId() // 25 
      return 0; 
     } 

     @Override 
     public void onPostExecute(Integer result) { 
      Thread.currentThread().getId() // 18 
      //hide dialog 
      //update text view -> CalledFromWrongThreadException!!! 
     } 
    } 

}

Quanto sopra sembra un uso vaniglia AsyncTask, ma ancora vedono questo problema si verifica anche nei casi più semplici come questo. Il prossimo esempio usa un'attività asincrona per eseguire altre attività asincrone. Forse c'è qualcosa che non so su cosa succede quando viene creato un task asincrono che sta causando un comportamento strano?

public class SomeActivity extends Activity implements TaskRunner.OnFinishListener { 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main_layout); 

     TaskRunner taskRunner = new TaskRunner(); 
     taskRunner.setOnFinishListener(this); 
     taskRunner.addTask(new SingleTask()); 
     taskRunner.addTask(new SingleTask()); 
     taskRunner.execute(); 
    } 

    @Override 
    public void onTaskFinish(List<Integer> results) { 
     //Thread id is 18 when it should be 1 
     //do something to a view - CalledFromWrongThreadException!! 
    } 

} 

//In a different file 
public class SingleTask extends AsyncTask<String, Void, Integer> { 
    //This is a an async task so we can run it separately as an asynctask 
    //Or run it on whatever thread runnerExecute is called on 
    @Override 
    public Integer doInBackground(String... params) { 
     return runnerExecute(params); 
    } 

    //Can be called outside of doInBackground 
    public Integer runnerExecute(String... params) { 
     //some long running task 
     return 0; 
    } 
} 

//In a different file 
public class TaskRunner { 

    private List<SingleTask> tasks; 
    private OnFinishListener onFinishListener; 

    public interface OnFinishListener { 
     public void onTaskFinish(List<Integer> results); 
    } 

    public TaskRunner() { 
     this.tasks = new ArrayList<SingleTask>(); 
    } 

    public void setOnFinishListener(OnFinishListener listener) { 
     this.onFinishListener = listener; 
    } 

    public void addTask(SingleTask task) { 
     tasks.add(task); 
    } 

    public void executeTasks() { 
     new RunnerTask().execute((SingleTask[]) tasks.toArray()); 
    } 

    //Calls the runnerExecute method on each SingleTask 
    private class RunnerTask extends AsyncTask<SingleTask, Integer, List<Integer>> { 
     @Override 
     public void onPreExecute() { 
      //Runs on thread 1 
     } 

     @Override 
     public List<Integer> doInBackground(SingleTask... params) { 
      //Runs on arbitrary thread 
      List<Integer> results = new ArrayList<Integer>(); 
      for(SingleTask task : params) { 
       int result =task.runnerExecute(task.getParams()); 
       results.add(result); 
      } 
      return results; 
     } 

     @Override 
     public void onPostExecute(List<Integer> results) { 
      //Runs on thread 18 
      onFinishListener.onTaskFinish(results); 
     } 
    } 
} 

Forse quello che sta succedendo qui è solo super strano, e non è affatto come asincrona compiti sono destinati ad essere utilizzati, in entrambi i casi sarebbe bello per andare a fondo della questione.

Fatemi sapere se avete bisogno di altro contesto.

+0

Puoi mostrare il codice rilevante? – Eric

+0

Non stai andando ad un altro 'Activity' prima che' AsyncTask' sia finito, vero? – codeMagic

+0

No, attendo fino al completamento dell'account async e quindi avvio un'attività in onPostExecuteMethod. Aggiungerò del codice rilevante alla mia domanda. – fxfilmxf

risposta

4

Ho riscontrato lo stesso problema e si è scoperto che il problema era l'utilizzo di Flurry 3.2.1. Tuttavia, il problema non è limitato alla libreria Flurry.

Il problema dietro le quinte sta avendo il primo in assoluto (quando l'app viene caricata per la prima volta) chiamata AsyncTask da un thread looper che non è il thread principale dell'interfaccia utente. Questa chiamata inizializza una variabile statica sHandler in AsyncTask sull'ID thread errato e questo id viene quindi utilizzato in tutte le successive chiamate AsyncTask $ onPostExecute().

Per risolvere il problema, chiamo AsyncTask vuoto (nulla da fare) sul primo caricamento dell'app, solo per inizializzare correttamente AsyncTask.

+0

Impressionante. Stavo pensando che qualcosa del genere stesse causando il problema, ma non sapevo della variabile statica in AsyncTask. Grazie mille!! – fxfilmxf

1

provare a utilizzare:

getBaseContext().runOnUiThread(new Runnable() 
{ 
@override 
public void run() 
{ 

} 
}); 

e scrivere il codice all'interno del run funzione di

+0

Grazie per la risposta. Stai suggerendo di inserirlo nel metodo onPostExecute? Se è così, sto già avvolgendo il mio codice onPostExecute in una chiamata a runOnUiThread che funziona bene. Sto ancora cercando di capire perché onPostExecute stia scappando dal thread principale, cosa che a mio avviso, non dovrebbe accadere. – fxfilmxf

+0

perché AsyncTask è un thread diverso rispetto al UIThread principale. Verrà eseguito in background mentre il thread principale sta eseguendo il codice corrente. Viene utilizzato per evitare il blocco dell'applicazione durante l'esecuzione di un codice che richiede molto tempo, ad esempio il download da Internet. Se AsyncTask viene eseguito dal thread principale, bloccherà l'applicazione mentre completa l'esecuzione del codice. Vorrei che fosse chiaro per te^_^ – KhalidTaha

+0

Non proprio sfortunatamente! Dovresti eseguire un asynctask dal thread principale, ma tutto il lavoro che farà l'attività asincrona avverrà in un thread separato. Dopo che il lavoro è finito suPostExecute viene chiamato e deve essere eseguito sul thread principale in modo da poter modificare gli elementi dell'interfaccia utente ecc. Mi chiedo perché onPostExecute non sia in esecuzione sul thread principale – fxfilmxf

0

Ho appena provato il codice e OnPreExecute e OnPostExecute viene eseguito sullo stesso thread, come si fa in uscita l'ID? prova:

Log.d("THREADTEST","PRE"+Long.toString(Thread.currentThread().getId())); 

Log.d("THREADTEST","BACKGROUND"+Long.toString(Thread.currentThread().getId())); 

Log.d("THREADTEST","POST"+Long.toString(Thread.currentThread().getId())); 

P.S. dovrebbe essere:

new SomeAsyncTask().execute(); 

e

private class SomeAsyncTask extends AsyncTask<String, Void, Integer> { ... } 
+0

Grazie per le modifiche, solo alcuni errori di battitura mentre stavo scrivendo, li aggiusterò. Sto trasmettendo l'id del thread nello stesso modo che stai descrivendo, con Thread.currentThread(). GetId(). Sulla maggior parte dei telefoni su cui eseguo i test, gli id ​​sono quelli che ti aspetteresti, ma posso riprodurre in modo affidabile il problema su una galassia s2 che ho. Vedo anche CalledFromWrongThreadException che si verifica nei telefoni degli altri utenti nei rapporti sugli arresti anomali. – fxfilmxf

+0

forse l'attività viene distrutta e ricreata mentre la tua asyntask è in esecuzione? per esempio. a causa di cambiamenti di orientamento. Ecco due buoni thread/articoli su di esso: –

+0

@fxfilmxf http://stackoverflow.com/questions/3821423/background-task-progress-dialog-orientation-change-is-there-any-100-working http: //blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ –

1

L'AsyncTask è progettato per essere utilizzato dal thread principale.Il tuo problema è il secondo caso, ed è che chiami execute sul SingleTask da un thread in background. Si chiama nel metodo doInBackground di RunnerTask. L'onPostExecute viene quindi eseguito dal backgroundthread di RunnerTask

Due opzioni per voi.

1: Trash RunnerTask, ed eseguire i SingleTasks da voi thread principale, faranno tutti corrono in parallell e non si sa che si conclude prima, ma OnPreExecute e OnPostExecute viene chiamato sul thread principale

2 : Cestino il SingleTask e definirli come Runnables, quindi è possibile eseguirli in sequenza nel doInBackground del RunnerTask. Verranno tutti eseguiti nel thread in background di RunnerTask, nell'ordine in cui viene chiamato Esegui. Al termine, onPostExecute di RunnerTask viene eseguito sul thread principale.

0

in realtà stai eseguendo il SingleTask dal metodo doinbackground di RunnerTask che non è corretto poiché asynctask deve essere eseguito solo da un thread principale. È necessario ricollegare la logica che esegue il set di SingleTasks da RunnerTask.

Problemi correlati