Scrittura di applicazioni reattive

Una delle chiavi per mantenere un'interfaccia utente grafica reattiva consiste nell'eseguire attività a esecuzione prolungata in un thread in background, in modo che l'interfaccia utente grafica non venga bloccata. Si supponga di voler calcolare un valore da visualizzare all'utente, ma tale valore richiede 5 secondi per calcolare:

public class ThreadDemo : Activity
{
    TextView textview;

    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);

        // Create a new TextView and set it as our view
        textview = new TextView (this);
        textview.Text = "Working..";

        SetContentView (textview);

        SlowMethod ();
    }

    private void SlowMethod ()
    {
        Thread.Sleep (5000);
        textview.Text = "Method Complete";
    }
}

Questa operazione funzionerà, ma l'applicazione verrà "bloccata" per 5 secondi mentre viene calcolato il valore. Durante questo periodo, l'app non risponderà ad alcuna interazione dell'utente. Per aggirare questo problema, si vogliono eseguire i calcoli su un thread in background:

public class ThreadDemo : Activity
{
    TextView textview;

    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);

        // Create a new TextView and set it as our view
        textview = new TextView (this);
        textview.Text = "Working..";

        SetContentView (textview);

        ThreadPool.QueueUserWorkItem (o => SlowMethod ());
    }

    private void SlowMethod ()
    {
        Thread.Sleep (5000);
        textview.Text = "Method Complete";
    }
}

Ora si calcola il valore in un thread in background in modo che l'interfaccia utente grafica rimanga reattiva durante il calcolo. Tuttavia, al termine del calcolo, l'app si arresta in modo anomalo, lasciando questo nel log:

E/mono    (11207): EXCEPTION handling: Android.Util.AndroidRuntimeException: Exception of type 'Android.Util.AndroidRuntimeException' was thrown.
E/mono    (11207):
E/mono    (11207): Unhandled Exception: Android.Util.AndroidRuntimeException: Exception of type 'Android.Util.AndroidRuntimeException' was thrown.
E/mono    (11207):   at Android.Runtime.JNIEnv.CallVoidMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue[] parms)
E/mono    (11207):   at Android.Widget.TextView.set_Text (IEnumerable`1 value)
E/mono    (11207):   at MonoDroidDebugging.Activity1.SlowMethod ()

Ciò è dovuto al fatto che è necessario aggiornare l'interfaccia utente grafica dal thread GUI. Il codice aggiorna l'interfaccia utente grafica dal thread ThreadPool, causando l'arresto anomalo dell'app. È necessario calcolare il valore nel thread in background, ma quindi eseguire l'aggiornamento nel thread GUI, gestito con Activity.RunOnUIThread:

public class ThreadDemo : Activity
{
    TextView textview;

    protected override void OnCreate (Bundle bundle)
    {
        base.OnCreate (bundle);

        // Create a new TextView and set it as our view
        textview = new TextView (this);
        textview.Text = "Working..";

        SetContentView (textview);

        ThreadPool.QueueUserWorkItem (o => SlowMethod ());
    }

    private void SlowMethod ()
    {
        Thread.Sleep (5000);
        RunOnUiThread (() => textview.Text = "Method Complete");
    }
}

Questo codice funziona come previsto. Questa interfaccia utente grafica rimane reattiva e viene aggiornata correttamente una volta che il calcolo è completo.

Si noti che questa tecnica non viene usata solo per calcolare un valore costoso. Può essere usato per qualsiasi attività a esecuzione prolungata che può essere eseguita in background, ad esempio una chiamata al servizio Web o il download di dati Internet.