Vous êtes sur la page 1sur 19

Cmo: Realizar llamadas seguras para subprocesos en controles de formularios Windows Forms

Visual Studio 2010 Otras versiones

Personas que lo han encontrado til: 0 de 1 Valorar este tema

Si utiliza multithreading para mejorar el rendimiento de las aplicaciones de Windows Forms, debe asegurarse de realizar llamadas a los controles de forma segura. El acceso a los controles de formularios Windows Forms no es inherentemente seguro para los subprocesos. Si dos o ms subprocesos manipulan el estado de un control, es posible obligar al control a pasar a un estado incoherente. Se pueden dar otros errores relacionados con los subprocesos, como condiciones de carrera e interbloqueos. Es importante asegurarse de que el acceso a los controles se realice de manera segura para los subprocesos. No es seguro llamar a un control desde un subproceso distinto del que cre el control sin utilizar el mtodo Invoke. A continuacin figura un ejemplo de una llamada que no es segura para los subprocesos. C# C++ VB // This event handler creates a thread that calls a // Windows Forms control in an unsafe way. private void setTextUnsafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsaf e)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // an unsafe call on the TextBox control. private void ThreadProcUnsafe() { this.textBox1.Text = "This text was set unsafely."; }

.NET Framework ayuda a detectar cundo el acceso a los controles se produce de una manera no segura para los subprocesos. Cuando se est ejecutando la aplicacin en un depurador y un subproceso distinto del que cre un control intenta llamar a ese control, el depurador inicia una excepcin InvalidOperationException con el mensaje: "Se tuvo acceso al control nombre del control desde un subproceso distinto a aquel en que lo cre". Esta excepcin aparece de forma fiable durante la depuracin y, en algunas circunstancias, en tiempo de ejecucin. Esta excepcin puede aparecer cuando se depuran aplicaciones escritas con una versin de .NET Framework anterior a .NET Framework versin 2.0. Cuando surja este problema, se recomienda corregirlo, si bien se puede deshabilitarlo estableciendo el valor de la propiedad CheckForIllegalCrossThreadCalls en false. Esto hace que el control se ejecute de la misma manera que si se ejecutara en Visual Studio .NET 2003 y .NET Framework 1.1.

Nota

Si usa controles ActiveX en un formulario, puede que se inicie la excepcin InvalidOperationException entre depurador. Si esto sucede, significa que los controles ActiveX no admiten multithreading. Para obtener ms in ActiveX con formularios Windows Forms, vea Aplicaciones de Windows Forms y aplicaciones no administra excepcin deshabilitando el proceso de hospedaje de Visual Studio. Para obtener ms informacin, vea Cmo alojamiento y Cmo: Deshabilitar el proceso de alojamiento y Cmo: Deshabilitar el proceso de alojamiento.

Realizar llamadas seguras para subprocesos a controles de Windows Forms


Para realizar una llamada segura para subprocesos a un control de Windows Forms
1. 2. 3. Consulte la propiedad InvokeRequired del control. Si la propiedad InvokeRequired devuelve true, llame a Invoke con un delegado que realice la llamada real al control. Si la propiedad InvokeRequired devuelve false, llame directamente al control.

En el siguiente ejemplo de cdigo, se implementa una llamada segura para subprocesos en el mtodo ThreadProcSafe, que el subproceso en segundo plano ejecuta. Si la propiedad InvokeRequired del control TextBox devuelve true, el mtodo ThreadProcSafe crea una instancia de SetTextCallback y la pasa al mtodo Invoke del formulario. Esto hace que se llame al mtodo SetText en el subproceso que ha creado el control TextBox, y en este contexto de subproceso se establece directamente la propiedad Text. C# C++ VB // This event handler creates a thread that calls a // Windows Forms control in a thread-safe way.privatevoid setTextSafeBtn_Click( object sender,

EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe) ); this.demoThread.Start(); } // This method is executed on the worker thread and makes// a t hread-safe call on the TextBox control.privatevoid ThreadProcSafe() { this.SetText("This text was set safely."); }

C# C++ VB // This method demonstrates a pattern for making thread-safe// calls on a Window s Forms control. //// If the calling thread is different from the thread that// created the TextBox control, this method creates a// SetTextCallback and calls i tself asynchronously using the// Invoke method.//// If the calling thread is the same as the thread that created// the TextBox control, the Text property is set directly. privatevoid SetText(string text) { // InvokeRequired required compares the thread ID of th e// calling thread to the thread ID of the creating thread.// If these threads a re different, it returns true.if (this.textBox1.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText ); this.Invoke(d, newobject[] { text }); } else { this.textBox1.Text = text; } }

Realizar llamadas seguras para subprocesos mediante BackgroundWorker


La mejor manera de implementar el multithreading en la aplicacin es utilizar el componente BackgroundWorker. El componente BackgroundWorker utiliza un modelo orientado a eventos para el multithreading. El subproceso en segundo plano ejecuta el controlador de eventos DoWork y el subproceso que crea los controles llama a los controladores de

eventos ProgressChanged y RunWorkerCompleted. Puede llamar a los controles desde los controladores de eventos RunWorkerCompleted yProgressChanged.

Para realizar llamadas seguras para subprocesos mediante BackgroundWorker


1. Cree un mtodo para que las operaciones que desee realizar se lleven a cabo en el subproceso en segundo plano. No llame a los controles creados por el subproceso principal en este mtodo. Cree un mtodo para notificar los resultados del trabajo en segundo plano una vez finalizado. Puede llamar a los controles creados por el subproceso principal en este mtodo. Enlace el mtodo creado en el paso 1 al evento DoWork de una instancia de BackgroundWorker y enlace el mtodo creado en el paso 2 al eventoRunWorkerCompleted de la misma instancia. Para iniciar el subproceso en segundo plano, llame al mtodo RunWorkerAsync de la instancia de BackgroundWorker.

2. 3.

4.

En el siguiente ejemplo de cdigo, el controlador de eventos DoWork utiliza Sleep para simular trabajo que tarda algn tiempo. No llama al control TextBox del formulario.La propiedad Text del control TextBox se establece directamente en el controlador de eventos RunWorkerCompleted. C# C++ VB // This BackgroundWorker is used to demonstrate the // preferred way of performi ng asynchronous operations.private BackgroundWorker backgroundWorker1;

C# C++ VB // This event handler starts the form's // BackgroundWorker by calling RunWorker Async.//// The Text property of the TextBox control is set// when the Background Worker raises the RunWorkerCompleted// event.privatevoid setTextBackgroundWorker Btn_Click( object sender, EventArgs e) { this.backgroundWorker1.RunWorkerAsync(); } // This event handler sets the Text property of the TextBox// c ontrol. It is called on the thread that created the // TextBox control, so the c all is thread-safe.//// BackgroundWorker is the preferred way to perform asynchr onous// operations.privatevoid backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { this.textBox1.Text = "This text was set safely by BackgroundWorker." ;

Asimismo, se puede notificar el progreso de una tarea en segundo plano mediante el evento ProgressChanged. Para obtener un ejemplo que incluye ese evento, veaBackgroundWorker.

Ejemplo
El siguiente ejemplo de cdigo es una aplicacin de Windows Forms completa que se compone de un formulario con tres botones y un cuadro de texto. El primer botn muestra el acceso no seguro entre subprocesos, el segundo botn muestra el acceso seguro mediante Invoke y el tercer botn muestra el acceso seguro medianteBackgroundWorker.

Nota

Para obtener instrucciones sobre cmo ejecutar el ejemplo, vea Cmo: Compilar y ejecutar un ejemplo de cd Studio. Este ejemplo requiere referencias a los ensamblados System.Drawing y System.Windows.Forms.
C# C++ VB using using using using System; System.ComponentModel; System.Threading; System.Windows.Forms;

namespace CrossThreadDemo { public class Form1 : Form { // This delegate enables asynchronous calls for setting // the text property on a TextBox control. delegate void SetTextCallback(string text); // This thread is used to demonstrate both thread-safe and // unsafe ways to call a Windows Forms control. private Thread demoThread = null; // This BackgroundWorker is used to demonstrate the // preferred way of performing asynchronous operations. private BackgroundWorker backgroundWorker1; private private private private TextBox textBox1; Button setTextUnsafeBtn; Button setTextSafeBtn; Button setTextBackgroundWorkerBtn;

private System.ComponentModel.IContainer components = null;

public Form1() { InitializeComponent(); } protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } // This event handler creates a thread that calls a // Windows Forms control in an unsafe way. private void setTextUnsafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsaf e)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // an unsafe call on the TextBox control. private void ThreadProcUnsafe() { this.textBox1.Text = "This text was set unsafely."; } // This event handler creates a thread that calls a // Windows Forms control in a thread-safe way. private void setTextSafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe) ); this.demoThread.Start(); } // This method is executed on the worker thread and makes // a thread-safe call on the TextBox control. private void ThreadProcSafe() { this.SetText("This text was set safely."); } // This method demonstrates a pattern for making thread-safe

// // // // // // // // //

calls on a Windows Forms control. If the calling thread is different from the thread that created the TextBox control, this method creates a SetTextCallback and calls itself asynchronously using the Invoke method. If the calling thread is the same as the thread that created the TextBox control, the Text property is set directly.

private void SetText(string text) { // InvokeRequired required compares the thread ID of th e // calling thread to the thread ID of the creating thre ad. // If these threads are different, it returns true. if (this.textBox1.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText ); this.Invoke(d, new object[] { text }); } else { this.textBox1.Text = text; } } // This event handler starts the form's // BackgroundWorker by calling RunWorkerAsync. // // The Text property of the TextBox control is set // when the BackgroundWorker raises the RunWorkerCompleted // event. private void setTextBackgroundWorkerBtn_Click( object sender, EventArgs e) { this.backgroundWorker1.RunWorkerAsync(); } // // // // // s // operations. private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { this.textBox1.Text = This event handler sets the Text property of the TextBox control. It is called on the thread that created the TextBox control, so the call is thread-safe. BackgroundWorker is the preferred way to perform asynchronou

"This text was set safely by BackgroundWorker." ; } #region Windows Form Designer generated code private void InitializeComponent() { this.textBox1 = new System.Windows.Forms.TextBox(); this.setTextUnsafeBtn = new System.Windows.Forms.Button (); this.setTextSafeBtn = new System.Windows.Forms.Button() ; this.setTextBackgroundWorkerBtn = new System.Windows.Fo rms.Button(); this.backgroundWorker1 = new System.ComponentModel.Back groundWorker(); this.SuspendLayout(); // // textBox1 // this.textBox1.Location = new System.Drawing.Point(12, 1 2); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(240, 20); this.textBox1.TabIndex = 0; // // setTextUnsafeBtn // this.setTextUnsafeBtn.Location = new System.Drawing.Poi nt(15, 55); this.setTextUnsafeBtn.Name = "setTextUnsafeBtn"; this.setTextUnsafeBtn.TabIndex = 1; this.setTextUnsafeBtn.Text = "Unsafe Call"; this.setTextUnsafeBtn.Click += new System.EventHandler( this.setTextUnsafeBtn_Click); // // setTextSafeBtn // this.setTextSafeBtn.Location = new System.Drawing.Point (96, 55); this.setTextSafeBtn.Name = "setTextSafeBtn"; this.setTextSafeBtn.TabIndex = 2; this.setTextSafeBtn.Text = "Safe Call"; this.setTextSafeBtn.Click += new System.EventHandler(th is.setTextSafeBtn_Click); // // setTextBackgroundWorkerBtn // this.setTextBackgroundWorkerBtn.Location = new System.D rawing.Point(177, 55); this.setTextBackgroundWorkerBtn.Name = "setTextBackgrou ndWorkerBtn"; this.setTextBackgroundWorkerBtn.TabIndex = 3; this.setTextBackgroundWorkerBtn.Text = "Safe BW Call";

this.setTextBackgroundWorkerBtn.Click += new System.Eve ntHandler(this.setTextBackgroundWorkerBtn_Click); // // backgroundWorker1 // this.backgroundWorker1.RunWorkerCompleted += new System .ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerC ompleted); // // Form1 // this.ClientSize = new System.Drawing.Size(268, 96); this.Controls.Add(this.setTextBackgroundWorkerBtn); this.Controls.Add(this.setTextSafeBtn); this.Controls.Add(this.setTextUnsafeBtn); this.Controls.Add(this.textBox1); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); this.PerformLayout(); } #endregion

[STAThread] static void Main() { Application.EnableVisualStyles(); Application.Run(new Form1()); } } }

Cuando se ejecuta la aplicacin y se hace clic en el botn Unsafe Call (Llamada no segura), aparece inmediatamente el texto "Written by the main thread" (Escrito por el subproceso principal) en el cuadro de texto. Dos segundos despus, cuando se intenta realizar la llamada no segura, el depurador de Visual Studio indica que se ha producido una excepcin. El depurador se detiene en la lnea del subproceso en segundo plano que intent escribir directamente en el cuadro de texto. Es preciso reiniciar la aplicacin para probar los otros dos botones. Cuando se hace clic en el botn Safe Call (Llamada segura), aparece el texto "Written by the main thread" (Escrito por el subproceso principal) en el cuadro de texto. Dos segundos despus, el cuadro de texto se establece en "Written by the background thread (Invoke)" (Escrito por el subproceso en segundo plano (Invoke)), lo que indica que se llam al mtodo Invoke. Cuando se hace clic en el botn Safe BW Call (Llamada segura a BW), aparece el texto "Written by the main thread" (Escrito por el subproceso principal) en el cuadro de texto. Dos segundos despus, el cuadro de texto se establece en "Written by the main thread after the background thread completed" (Escrito por el subproceso principal una vez completado el subproceso en segundo plano), lo que indica que se llam al controlador del evento RunWorkerCompleted de BackgroundWorker.

Programacin eficaz
Precaucin

Cuando se utiliza el multithreading de cualquier ordenacin, el cdigo se puede exponer a errores graves y com veaProcedimientos recomendados para el subprocesamiento administrado antes de implementar cualquier solu

Vea tambin
Tareas Cmo: Ejecutar una operacin en segundo plano Cmo: Implementar un formulario que utiliza una operacin en segundo plano Referencia BackgroundWorker Otros recursos Desarrollar controles personalizados de formularios Windows Forms con .NET Framework Aplicaciones de Windows Forms y aplicaciones no administradas
Le ha resultado til? S

El cdigo para C#.


Aqu te muestro los mismos trozos de cdigo que hay en el texto. El cdigo completo lo puedes conseguir en el fichero zip.

Como "bono" especial, estas dos funciones que simulan las funciones homnimas (del mismo nombre) de Visual Basic: private string getSetting(string appName, string section, string key, string sDefault) { // Los datos de VB se guardan en: // HKEY_CURRENT_USER\Software\VB and VBA Program Settings RegistryKey rk = Registry.CurrentUser.OpenSubKey(@"Software\VB and VBA Program Settings\" + appName + "\\" + section); string s = sDefault; if( rk != null ) s = (string)rk.GetValue(key);

// return s; } private void saveSetting(string appName, string section, string key, string setting) { // Los datos de VB se guardan en: // HKEY_CURRENT_USER\Software\VB and VBA Program Settings RegistryKey rk = Registry.CurrentUser.CreateSubKey(@"Software\VB and VBA Program Settings\" + appName + "\\" + section); rk.SetValue(key, setting); }

// Estos mtodos se usarn para producir los eventos de esta clase. protected void OnProcesandoFichero(string sMsg) { // Este evento se produce cuando se est procesando un fichero // por uno de los threads. // Desde aqu producimos nuestro evento a la aplicacin. if( ProcesandoFichero != null ) ProcesandoFichero(sMsg); } protected void OnProcesandoDirectorio(string sMsg) { // Este evento se produce cuando se est procesando un directorio // por uno de los threads. // Desde aqu producimos nuestro evento a la aplicacin. if( ProcesandoDirectorio != null ) ProcesandoDirectorio(sMsg); }

// Crear un thread para cada directorio a procesar cProcesarFicheroThread oPF = new cProcesarFicheroThread(lasPalabras); // asignar el manejador del evento (17/Ene/04)

oPF.ProcesandoFichero += new cProcesarFicheroThread.ProcesandoFicheroDelegate(this.OnProcesandoFiche ro); oPF.ProcesandoDirectorio += new cProcesarFicheroThread.ProcesandoDirectorioDelegate(this.OnProcesandoDi rectorio); // asignar los datos del directorio y la extensin a comprobar oPF.sDir = sDir; oPF.sExt = sExt; // Los procedimientos a usar no deben aceptar parmetros oPF.esteThread = new Thread(new ThreadStart(oPF.ProcesarDir)); // mProcesarFic.Add(oPF); // // Iniciar el thread oPF.esteThread.Start();

private void desglosar(string sText, string sFic){ // Desglosar en palabras individuales el contenido del parmetro // // Estos signos se considerarn separadores de palabras string sSep = @".,;: ()<>[]{}?!/\'=+*-%&$|#@" + "\"\t\n\r";" = sText.Split(sSep.ToCharArray()); // crear un array con cada una de las palabras string[] palabras cPalabra tPalabra; int i; // for(i = 0; i <= palabras.Length - 1; i++){ string s = palabras[i]; // slo si no es una cadena vaca if( s != "" ){ if( lasPalabras.Exists(s) == false ){ tPalabra = new Guille.Clases.cPalabra(); tPalabra.ID = s; tPalabra.Veces = 1; // el fichero en el que inicialmente apareci

tPalabra.Contenido = sFic; lasPalabras.Add(tPalabra); }else{ // Incrementar el nmero de esta palabra lasPalabras[s].IncVeces(); } } } }

public virtual void Add(cPalabra tPalabra) { // Aadir un nuevo elemento a la coleccin // // bloquear este mtodo por si se usan Threads (17/Ene/04) lock(this.GetType()) { if( !m_ht.ContainsKey(tPalabra.ID) ) m_ht.Add(tPalabra.ID, tPalabra); } // lock }

public virtual cPalabra this[string sIndex] { get{ // bloquear esta propiedad por si se usan Threads (17/Ene/04) lock(this.GetType()) { cPalabra tPalabra; // if( m_ht.ContainsKey(sIndex) ) {

return (cPalabra)m_ht[sIndex]; } else { // Creamos una nuevo objeto tPalabra = new cPalabra(); tPalabra.ID = sIndex; // lo aadimos a la coleccin m_ht.Add(sIndex, tPalabra); // return tPalabra; } } // lock } }

public void ProcesarDir() { //----------------------------------------------------------------// Procesar los ficheros del path y extensin indicadas. // Convertido en Sub para usar con Thread (25/Mar/01) // // Modificaciones para adaptarlo a la versin definitiva (17/Ene/04) //----------------------------------------------------------------// string[] tFiles; int i, n; string s; System.IO.StreamReader sr; // // Asigna a tFiles un array con los nombres de los ficheros, // path incluido

tFiles = Directory.GetFiles(sDir, sExt); // //Debug.WriteLine(sDir) // // Examinar el contenido de cada fichero y trocearlo en palabras // n = tFiles.Length - 1; // // este evento se ir produciendo de forma independiente al orden // en el que se procesen los ficheros... // y realmente no producir el efecto deseado: // que se vaya mostrando cada directorio conforme se procesan los ficheros if( ProcesandoDirectorio != null ) ProcesandoDirectorio(String.Format("Dir: {0}, con {1} ficheros.", sDir, n + 1)); // un respiro para que se actualice la informacin (no funciona siempre) //esteThread.Sleep(10) TotalDirectorios += 1; // for(i = 0; i <= n; i++){ if( ProcesandoFichero != null ) ProcesandoFichero(" Procesando: " + (i + 1).ToString() + " / " + (n + 1).ToString() + " " + Path.GetFileName(tFiles[i]) + " ..."); // si se pone aqu, si que se ir mostrando //if( ProcesandoDirectorio != null ) // ProcesandoDirectorio(String.Format("Dir: {0}, con {1} ficheros.", sDir, n + 1)); //esteThread.Sleep(50) // // Abrir el fichero usando la codificacin estndard de Windows sr = new StreamReader(tFiles[i], System.Text.Encoding.Default); s = sr.ReadToEnd(); sr.Close(); // // Buscar cada una de las palabras del fichero desglosar(s, tFiles[i]); }

// TotalFicheros += tFiles.Length; }

private void procesarSubDir(string sDir) { procesarSubDir(sDir, "*.*"); } private void procesarSubDir(string sDir, string sExt) { //----------------------------------------------------------------// Este procedimiento ser llamado de forma recursiva para procesar // cada uno de los directorios. //----------------------------------------------------------------string[] tSubDirs; // tSubDirs = Directory.GetDirectories(sDir); // // Crear un thread para cada directorio a procesar cProcesarFicheroThread oPF = new cProcesarFicheroThread(lasPalabras); // asignar el manejador del evento (17/Ene/04) oPF.ProcesandoFichero += new cProcesarFicheroThread.ProcesandoFicheroDelegate(this.OnProcesandoFiche ro); oPF.ProcesandoDirectorio += new cProcesarFicheroThread.ProcesandoDirectorioDelegate(this.OnProcesandoDi rectorio); // asignar los datos del directorio y la extensin a comprobar oPF.sDir = sDir; oPF.sExt = sExt; // Los procedimientos a usar no deben aceptar parmetros oPF.esteThread = new Thread(new ThreadStart(oPF.ProcesarDir)); // mProcesarFic.Add(oPF);

// // Iniciar el thread oPF.esteThread.Start(); // // Llamada recursiva a este procedimiento para procesar los subdirectorios // de cada directorio foreach(string tDir in tSubDirs) // Procesar todos los directorios de cada subdirectorio procesarSubDir(tDir, sExt); }

public string Procesar(string sDir) { return Procesar(sDir, "*.*", false); } public string Procesar(string { return Procesar(sDir, sExt, false); } public string Procesar(string { //----------------------------------------------------------------// Procesar los directorios del path indicado en sDir, // buscando ficheros con la extensin sExt, // y si se deben procesar los subdirectorios. // // Si se quieren usar varias extensiones se podra hacer, // pero hay que tener en cuenta que Directory.GetFiles // no procesar varias extensiones separadas por ; // Por tanto, habr que hacer un bucle para cada extensin, // pero eso se har en el mtodo ProcesarDir de la clase // cProcesarFicheroThread. //----------------------------------------------------------------sDir, string sExt, bool conSubDir) sDir, string sExt)

// mProcesarFic.Clear(); // // Comprobar si se van a procesar los subdirectorios if( conSubDir ) procesarSubDir(sDir, sExt); else{ // Crear un thread para cada directorio a procesar cProcesarFicheroThread oPF = new cProcesarFicheroThread(lasPalabras); // asignar el manejador del evento (17/Ene/04) oPF.ProcesandoFichero += new cProcesarFicheroThread.ProcesandoFicheroDelegate(this.OnProcesandoFiche ro); oPF.ProcesandoDirectorio += new cProcesarFicheroThread.ProcesandoDirectorioDelegate(this.OnProcesandoDi rectorio); // asignar los datos del directorio y la extensin a comprobar oPF.sDir = sDir; oPF.sExt = sExt; // Los procedimientos a usar no deben aceptar parmetros oPF.esteThread = new Thread(new ThreadStart(oPF.ProcesarDir)); // mProcesarFic.Add(oPF); // Iniciar el thread oPF.esteThread.Start(); } // // Aqu llegar incluso antes de terminar todos los threads //Debug.WriteLine("Fin de Procesar todos los ficheros") // // Comprobar si han terminado los threads int i, j; do{ j = 0; for(i = 0; i <= mProcesarFic.Count - 1; i++){ // Comprobar si alguno de los Threads est "vivo" // si es as, indicarlo para que contine el bucle

if( ((cProcesarFicheroThread)mProcesarFic[i]).esteThread.IsAlive ){ j = 1; break; } } // Esto es necesario, para que todo siga funcionando System.Windows.Forms.Application.DoEvents(); }while( j == 1); // //Debug.WriteLine("Han finalizado los threads") // // Ahora podemos asignar el nmero de ficheros procesados i = cProcesarFicheroThread.TotalDirectorios; j = cProcesarFicheroThread.TotalFicheros; System.Text.StringBuilder sb = new System.Text.StringBuilder(); // sb.AppendFormat("Procesado: {0} dir., ", i); if( j == 1 ){ sb.AppendFormat("{0} fichero.", j); }else{ sb.AppendFormat("{0} ficheros.", j); } // Producimos el evento... si est interceptado if( FicherosProcesados != null) FicherosProcesados(sb.ToString()); // // Asignamos a cero el valor de total ficheros // por si se vuelve a usar, para que no siga acumulando. cProcesarFicheroThread.TotalFicheros = 0; cProcesarFicheroThread.TotalDirectorios = 0; // return sb.ToString(); }

Vous aimerez peut-être aussi