Escritura de aplicaciones con capacidad de respuesta

Una de las claves para mantener una interfaz gráfica de usuario con capacidad de respuesta es realizar tareas de ejecución prolongada en un subproceso en segundo plano para que la interfaz no se bloquee. Supongamos que queremos calcular un valor que se va a mostrar al usuario, pero ese valor tarda 5 segundos en calcularse:

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";
    }
}

Esto funcionará, pero la aplicación se "bloqueará" durante 5 segundos mientras se calcula el valor. Durante este tiempo, la aplicación no responderá a ninguna interacción del usuario. Para solucionar este problema, queremos realizar nuestros cálculos en un subproceso en segundo plano:

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";
    }
}

Ahora calculamos el valor en un subproceso en segundo plano para que la interfaz gráfica de usuario siga respondiendo durante el cálculo. Sin embargo, cuando el cálculo finaliza, la aplicación se bloquea y lo deja en el registro:

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 ()

Esto se debe a que la interfaz gráfica de usuario se debe actualizar desde el subproceso de la interfaz. Nuestro código actualiza la interfaz gráfica de usuario desde el subproceso de ThreadPool, lo que hace que la aplicación se bloquee. Es necesario calcular nuestro valor en el subproceso en segundo plano y, después, realizar la actualización en el subproceso de la interfaz gráfica de usuario, que se controla 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");
    }
}

Este código funciona según lo previsto. Esta interfaz gráfica de usuario mantiene la capacidad de respuesta y se actualiza correctamente una vez que se completa el cálculo.

Tenga en cuenta que esta técnica no solo se usa para calcular valores dependientes. Se puede usar para cualquier tarea de ejecución prolongada que se pueda realizar en segundo plano, como una llamada de servicio web o la descarga de datos de Internet.