Pedro Rui Silva

Development in .Net

My Links

Blog Stats

Story Categories

Archives

Post Categories

Image Galleries

Login

Invocação assincrona de métodos e como passar parâmetros para uma Thread

Há dias colocaram-me a seguinte questão, como passar parâmetros para uma Thread, e dentro dessa Thread como era possível escrever valores para um controlo (no caso uma TextBox), de modo a que a interface com o utilizador não ficasse bloqueada. A questão resume-se ao seguinte, como invocar assincronamente um método e permitir que esse método possa dar informações durante a sua execução.

A resposta é invariavelmente a mesma de sempre, há várias formas de o fazer, a forma como os parâmetros são passados para uma Thread varia inclusive com a versão da .Net Framework. Antes de escrever este post, fui ver o que se tinha escrito sobre a passagem de parâmetros para uma Thread, e encontrei 2 posts interessantes, um do Israel Aéce e outro do Luís Abreu. Em linhas gerais dizem tudo o que é necessário sobre a forma de passar parâmetros para uma Thread, mas em relação a esta questão há duas coisas que acho que falta dizer, a primeira prende-se com o facto de que tanto o delegate ParameterizedThreadStart (criar uma Thread) como o método QueueUserWorkItem (ThreadPool) passarem para o método um parâmetro do tipo object, isto vai provocar Boxing e Unboxing na passagem desse parâmetro, além disso essa chamada não é Type Safe.
Tendo em conta isto e como resposta a questão inicial, penso que a forma mais elegante e segura de o fazer é encapsulando a Thread, e utilizar um delegate, para que o método em execução possa ir fazendo output.

A classe que vai encapsular a Thread é a seguinte:

    internal sealed class WorkerThread

    {

        private int loops;

        private ThreadOutputCallback threadOutputCallback;

 

        /// <summary>

        /// Initializes a new instance of the <see cref="T:WorkerThread"/> class.

        /// </summary>

        /// <param name="outputCallback">The outputCallback.</param>

        internal WorkerThread(ThreadOutputCallback outputCallback)

        {

            threadOutputCallback = outputCallback;

        }

 

        /// <summary>

        /// Execute the specified lines.

        /// </summary>

        /// <param name="lines">The lines.</param>

        internal void Loop(int lines)

        {

            //The the number of loops

            loops = lines;

            //Create the new thread object

            Thread thread = new Thread(new ThreadStart(Run));

            //Start the thread

            thread.Start();

        }

 

        /// <summary>

        /// Runs the specified argument.

        /// </summary>

        private void Run()

        {

            //Set the start value

            int count = 0;

            //Call the callback "loop" times

            while (count++ < loops)

            {

                string message = string.Format("Loop {0} from {1}", count, loops);

                WriteOutput(message);

            }

            WriteOutput("I'm done.");

        }

 

        /// <summary>

        /// Writes the output.

        /// </summary>

        /// <param name="message">The message.</param>

        private void WriteOutput(string message)

        {

            if (threadOutputCallback != null)

            {

                threadOutputCallback(message);

            }

        }

    }

Não é a solução mais fácil ou imediata, mas deste modo evita-se fazer Boxing e Unboxing, e a forma como a Thread é iniciada (criar uma Thread ou utilizar a ThreadPool) fica encapsulada. Além disso a chamada ao método Loop é Type Safe, e recorrendo a um delegate, pode ser feito output da execução do método, este delegate é definido neste caso da seguinte forma:

    internal delegate void ThreadOutputCallback(string message);

Isto resolve parte da questão, mas levanta uma outra, o output. O método para o qual aponta o delegate, está a ser executado numa Thread diferente, é necessário então garantir que a chamada ao método é executada na Thread correcta fazendo o seguinte:

        private void ShowInfo(string message)

        {

            //Check if the method for the control is invoked in the correct thread

            if (listBox.InvokeRequired)

            {

                //Create the new delegate to invoke the method in the correct thread

                listBox.BeginInvoke(new ThreadOutputCallback(ShowInfo), new object[] {message});

            }

            else

            {

                //Update the text

                listBox.Items.Add(message);

            }

        }

Ao criar o objecto é criado o delegate para o método de output, e a partir daqui pode ser invocado o método Loop:

                WorkerThread worker = new WorkerThread(new ThreadOutputCallback(ShowInfo));

                worker.Loop(lines);

Desta forma é possível invocar um método que vai ser executado numa outra Thread, e ao mesmo tempo permitir-lhe enviar informação durante a execução. Como disse no início há várias maneiras de invocar métodos de forma assíncrona, esta é apenas uma alternativa, outra é por exemplo a utilização do delegate AsyncCallback, para invocar um método assincronamente, existe muita informação que podem consultar sobre Asynchronous Programming no MSDN.

Demo Project

posted on Friday, July 07, 2006 1:26 AM

Feedback

No comments posted yet
Title  
Name  
Url
Box Code
Protected by FormShield
Comments