viernes, 10 de agosto de 2012

El subproceso actual debe establecerse en modo de subprocesamiento controlado simple (Single Thread Apartment, STA) para poder realizar llamadas OLE. Asegúrese de que la función Main tienen marcado STATThreadAttribute. Esta excepción solo se desencadena si se adjunta un depurador al proceso.

Estamos desarrollando una aplicación Windows Forms sobre Framework 4.0 (también aplicable en versiones anteriores) y nos encontramos el siguiente error cuando nuestro código interactua con algún tipo de objeto OLE, por ejemplo componentes de la UI de Windows.Forms como Form, ListBox, UserControl, DialogBox, etc ...
El subproceso actual debe establecerse en modo de subprocesamiento controlado simple (Single Thread Apartment, STA) para poder realizar llamadas OLE. Asegúrese de que la función Main tienen marcado STATThreadAttribute. Esta excepción solo se desencadena si se adjunta un depurador al proceso. 
El error nos indica que estamos realizando alguna operación no permitida entre Threads. Posiblemente si hemos llegado a este punto es porque por algún motivo nos interesa que nuestra aplicación tenga más de un hilo de ejecución al realizar determinadas acciones.

Remarcar que en función de como tengamos configurado Visual Studio, podríamos llegar a recibir un error diferente al del título de la entrada.
Operación no válida a través de subprocesos: Se tuvo acceso al control 'xxx' desde un subproceso distinto a aquel en que lo creó.
Si creamos un nuevo proyecto de tipo Aplicación de Windows Forms desde Visual Studio, veremos que por defecto incorpora un formulario Form1.cs y la clase Program.cs que contiene un método Main.
namespace ThreadSamples
{
    static class Program
    {
        /// <summary>
        /// Punto de entrada principal para la aplicación.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
Es importante fijarnos en el atributo STAThread del método Main. En caso que nuestra aplicación no tenga este atributo seguramente ese sea el origen del problema. Es imposible explicar a fondo como Windows y .NET se comportan con objetos COM en un solo artículo de modo conciso, me quedaría con el concepto de que nuestra aplicación windows necesita definir un hilo principal en el single-thread apartment (STA) y por tanto el primer formulario que se muestre en nuestra aplicación debe lanzarse desde un método marcado con el atributo STAThread.

Si salvado este primer caso seguimos teniendo problemas problablemente la causa sea que hayamos ejecutado procesos en background una vez lanzado el formulario principal y alguno de estos procesos intenta interactuar con algún control de la UI que no ha sido creado desde el mismo hilo de ejecución en el que se está ejecutando el proceso.

Vamos a reproducir el problema con un ejemplo sencillo.
  • Thread Principal (P) -> Se encarga de lanzar el formulario principal que contiene un elemento TextBox y un Button.

  • Thread Secundario (S) -> Realiza un proceso que se lanza al pulsar el Button de nuestro formulario y al finalizar modifica el valor del TextBox para indicar la fecha exacta en que ha acabado el proceso.


El siguiente código nos servirá como base para reproducir el error y posteriormente ver como solucionarlo:
namespace ThreadSamples
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //creamos nuevo hilo de ejecución para lanzar el método Proceso
            Thread procesoBackGround = new Thread(new ThreadStart(Proceso));
            procesoBackGround.Start();
        }

        private void Proceso() 
        { 
            //simulamos proceso parando ejecución 5 segundos...
            Thread.Sleep(5000);
            //actualizamos fecha en que finaliza la ejecución del proceso.
            textBox1.Text = DateTime.Now.ToString();
        }
    }
}
Al ejecutar y pulsar el botón, en el momento en que la aplicación se dispone a modificar la propiedad Text de la caja de texto, se genera una excepción.
Operación no válida a través de subprocesos: Se tuvo acceso al control 'textBox1' desde un subproceso distinto a aquel en que lo creó.
Veamos como implementar código ThreadSafe que evitará el problema, necesitaremos hacer los siguiente ajustes en el código:

  • Crear un método privado en Form1 que se encargará de interactuar con el TextBox.
    private void InteractuarUI(string pFechaFinProceso) 
    {
      textBox1.Text = pFechaFinProceso;
    }
    
  • Crear un delegado con los mismos parámetros de entrada y salida del método que acabamos de crear.
    public delegate void FinProceso(string pValue);
    
  • Cambiar la línea del método Form1.Proceso() en la que asignamos valor a textbox1.Text por el siguiente fragmento de código.
    this.Invoke(new FinProceso(InteractuarUI),DateTime.Now.ToString());
    

Con esto ya tendríamos una implementación ThreadSafe de nuestro proceso y no debería generarse ninguna excepción. El método Invoke() está implicito en todo los controles que heredan de Windows.Forms y garantiza que .NET se encargará mediante el contexto de sincronización que el intercambio de información entre los hilos es seguro. Básicamente lo que hace .NET es asegurarse que el trabajo "conflictivo" lo hará el Thread que corresponda, en este caso (P).

Existen otras técnicas para solucionar el problema que a priori resultan más complejas pero a su vez permiten controlar más aspectos del comportamiento MultiThreading.
  • En la versiones más recientes del Framework, .NET distribuye la clase SynchronizationContext a partir de la cual podríamos intercambiar información entre diferentes hilos de modo seguro.

  • Existe el componente BackGroundWorker disponible en aplicaciones Windows Forms que permite gestionar la comunicación entre procesos en segundo plano, recomendado especialmente si estos han de interactuar con la UI.

Hasta aquí el artículo, espero os resulte útil. No olvidéis consultar el archivo de areaTIC hay artículos que podrían interesarte!


4 comentarios:

Anónimo dijo...

Te soy honesto, me salvaste, casi me corto el "instrumento". Resulta que intentaba copiar una string al clipboard desde un thread y a 15 minutos de entregar el laburo me salto este error.
Gracias totales.

Anónimo dijo...

Muchas gracias. A mi también me ha solucionado el problema.

Anónimo dijo...

Yo tengo el mismo problema al momento de copiar un string al clipboard, como me quedaría el codigo, no logro solucionarlo.

Su ayuda gracias.

Anónimo dijo...

Muchas gracias, me solucionó el problema

Publicar un comentario