2011-01-15 19 views
12

Ahora tengo el código C# para engendrar una nueva ventana en un hilo diferente, esto funciona, pero tan pronto como se abre la nueva ventana engendrada, se cierra y el hilo termina ¿Cómo podría hacerlo para que la nueva ventana engendrada se pueda cerrar desde el primer hilo?engendrar un nuevo hilo para abrir una nueva ventana y cerrarlo desde un hilo diferente

Aquí es un "árbol" de cómo funciona actualmente el desove:

hilo principal
--Uses una función en el hilo principal para iniciar otra función en un hilo separado para abrir la ventana w, causando la ventana para usar ese hilo.

Básicamente solo quiero que las dos ventanas tengan su propio hilo. Y poder controlar la ventana secundaria generada desde el primer hilo de la ventana.

Respuesta

15

Esto es solo un ejemplo rápido. Es un poco más robusto que el primero que escribí. Elimina la condición de carrera existente mediante el uso de p/invoke.

Actualizado Todavía tenía una condición de carrera. Este debe ser perfecto.

using System; 
using System.Drawing; 
using System.Runtime.InteropServices; 
using System.Threading; 
using System.Windows.Forms; 

class MainUIThreadForm : Form 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Application.Run(new MainUIThreadForm()); 
    } 

    private IntPtr secondThreadFormHandle; 

    public MainUIThreadForm() 
    { 
     Text = "First UI"; 
     Button button; 
     Controls.Add(button = new Button { Name = "Start", Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) }); 
     button.Click += (s, e) => 
     { 
      if (secondThreadFormHandle == IntPtr.Zero) 
      { 
       Form form = new Form 
       { 
        Text = "Second UI", 
        Location = new Point(Right, Top), 
        StartPosition = FormStartPosition.Manual, 
       }; 
       form.HandleCreated += SecondFormHandleCreated; 
       form.HandleDestroyed += SecondFormHandleDestroyed; 
       form.RunInNewThread(false); 
      } 
     }; 
     Controls.Add(button = new Button { Name = "Stop", Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40), Enabled = false }); 
     button.Click += (s, e) => 
     { 
      if (secondThreadFormHandle != IntPtr.Zero) 
       PostMessage(secondThreadFormHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); 
     }; 
    } 

    void EnableStopButton(bool enabled) 
    { 
     if (InvokeRequired) 
      BeginInvoke((Action)(() => EnableStopButton(enabled))); 
     else 
     { 
      Control stopButton = Controls["Stop"]; 
      if (stopButton != null) 
       stopButton.Enabled = enabled; 
     } 
    } 

    void SecondFormHandleCreated(object sender, EventArgs e) 
    { 
     Control second = sender as Control; 
     secondThreadFormHandle = second.Handle; 
     second.HandleCreated -= SecondFormHandleCreated; 
     EnableStopButton(true); 
    } 

    void SecondFormHandleDestroyed(object sender, EventArgs e) 
    { 
     Control second = sender as Control; 
     secondThreadFormHandle = IntPtr.Zero; 
     second.HandleDestroyed -= SecondFormHandleDestroyed; 
     EnableStopButton(false); 
    } 

    const int WM_CLOSE = 0x0010; 
    [DllImport("User32.dll")] 
    extern static IntPtr PostMessage(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam); 
} 

internal static class FormExtensions 
{ 
    private static void ApplicationRunProc(object state) 
    { 
     Application.Run(state as Form); 
    } 

    public static void RunInNewThread(this Form form, bool isBackground) 
    { 
     if (form == null) 
      throw new ArgumentNullException("form"); 
     if (form.IsHandleCreated) 
      throw new InvalidOperationException("Form is already running."); 
     Thread thread = new Thread(ApplicationRunProc); 
     thread.SetApartmentState(ApartmentState.STA); 
     thread.IsBackground = isBackground; 
     thread.Start(form); 
    } 
} 

Aquí es el primer ejemplo para la posteridad:

using System; 
using System.Drawing; 
using System.Threading; 
using System.Windows.Forms; 

class MainUIThreadForm : Form 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Application.Run(new MainUIThreadForm()); 
    } 

    SecondUIThreadForm secondThreadForm; 
    public MainUIThreadForm() 
    { 
     Text = "First UI"; 
     Button button; 
     Controls.Add(button = new Button { Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) }); 
     button.Click += (s, e) => 
      { 
       if (secondThreadForm == null || !secondThreadForm.IsHandleCreated) 
        secondThreadForm = SecondUIThreadForm.Create(); 
      }; 
     Controls.Add(button = new Button { Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40) }); 
     button.Click += (s, e) => 
     { 
      if (secondThreadForm != null && secondThreadForm.IsHandleCreated) 
       secondThreadForm.Invoke((Action)(() => secondThreadForm.Close())); 
     }; 
    } 
} 

class SecondUIThreadForm : Form 
{ 
    static void Main2(object state) 
    { 
     Application.Run((Form)state); 
    } 

    public static SecondUIThreadForm Create() 
    { 
     SecondUIThreadForm form = new SecondUIThreadForm(); 
     Thread thread = new Thread(Main2); 
     thread.SetApartmentState(ApartmentState.STA); 
     thread.Start(form); 
     return form; 
    } 

    public SecondUIThreadForm() 
    { 
     Text = "Second UI"; 
    } 
} 
+0

¿Cerrará el formulario para limpiar el hilo y el formulario correctamente? Intenté poner mi segunda forma en un "uso" pero se colgó la aplicación porque la eliminación fue llamada desde el hilo equivocado – chrispepper1989

+1

@ chrispepper1989 Parece que su uso no está en 'ApplicationRunProc' que se ejecuta en el hilo que crea la forma y sus controles. Tal vez podría preguntar esto como una nueva pregunta y vincularlo aquí. De esa manera podemos ver el código. Produzca un ejemplo simple como el anterior que duplica el comportamiento que está viendo. – Tergiver

1

¿Cómo se creó la nueva ventana a partir del segundo hilo? ¿Y qué hace el hilo después de que se crea la ventana?

Sin ver el código, supongo que el problema es que su segundo hilo no bombea mensajes en la cola de mensajes de Windows.

¿Está llamando a Application.Run en su segundo hilo?

BTW: tenga en cuenta que su diseño tiene algunas limitaciones. El primer hilo no podrá directamente controlar la segunda ventana. Siempre que intente manipular cualquier elemento de UI en la segunda ventana desde el primer subproceso, tendrá que usar Control.Invoke para asegurarse de que la manipulación real de la interfaz de usuario ocurra en el subproceso correcto.

26

apuesto lo que está haciendo es algo como esto:

new Thread(() => new TestForm().Show()).Start(); 

porque esto hace que la ventana desaparece de inmediato, al igual que usted describe.

Tal vez puedas probar:

new Thread(() => new TestForm().ShowDialog()).Start(); 

ShowDialog teje su propio suministro de mensajes, y sólo regresa cuando la ventana está cerrada.

+0

Buena solución. Esto también resolvió mi problema cuando recibía una excepción después de llamar a 'Application.Run (form);' en el nuevo hilo, lo que hacía que el formulario en el hilo principal no se pudiera cerrar. – dahvyd

1

Estoy escribiendo una aplicación que se enrosca y utiliza la interfaz de usuario en el hilo creado para despachar dibujo funcionalidad a la DC. Cuando portamos la aplicación para ejecutarla desde el símbolo del sistema, quedamos naturalmente con un problema ya que no se creó o necesitó el subproceso - entonces creé otro hilo desde el punto de entrada de la aplicación que esencialmente llamaba ShowDialog () (la única forma de hacer girar la bomba de mensajes) en el formulario principal, con OnShown overidden para ocultar y minimizar permanentemente el formulario cuando se realiza la llamada.Esto me permitió seguir enviando al formulario y manejar todo mi dibujo desde los otros hilos múltiples.

Sin duda es un enfoque desagradable, pero esta fue una manera rápida de hacerlo, y funciona como se esperaba.

2

Puede hacerlo de esta manera:

En Program.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Windows.Forms; 
using System.Threading; 

namespace TwoWindows 
{ 
    static class Program 
    { 
     public static Form1 form1; 
     public static Form2 form2; 
     /// <summary> 
     /// The main entry point for the application. 
     /// </summary> 
     [STAThread] 
     static void Main() 
     { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 

      form1 = new Form1(); 
      form2 = new Form2(); 

      form1.Form2Property = form2; 
      form2.Form1Property = form1; 

      form1.Show(); 
      form2.Show(); 

      Application.Run(); 
     } 
    } 
} 

En Form1.cs:

namespace TwoWindows 
{ 
    public partial class Form1 : Form 
    { 
     public Form2 Form2Property { get; set; } 

     public Form1() 
     { 
      InitializeComponent(); 
     } 

     protected override void OnClosed(EventArgs e) 
     { 
      if (Form2Property.IsDisposed) 
       Application.Exit(); 
     } 
    } 
} 

Y Form2.cs:

namespace TwoWindows 
{ 
    public partial class Form2 : Form 
    { 
     public Form1 Form1Property { get; set; } 

     public Form2() 
     { 
      InitializeComponent(); 
     } 

     protected override void OnClosed(EventArgs e) 
     { 
      if (Form1Property.IsDisposed) 
       Application.Exit(); 
     } 
    } 
} 

De esta forma puedes obtener dos formas en el sam e hilo y use uno para controlar el otro. Si necesita utilizar subprocesos, le sugiero que use subprocesos dedicados que forman parte de las clases y que no se generen en un método que se puede llamar más de una vez. Luego use ManualResetEvent o AutoResetEvent para controlar el procesamiento de subprocesos. Me gusta mucho el enfoque de usar algo como esto, porque es seguro y no gasta muchos recursos en inicializar hilos.

public class MyClassOrForm 
{ 
    Thread myProcessingThread; 
    public AutoResetEvent startProcessing = new AutoResetEvent(false); 
    public AutoResetEvent processingFinished = new AutoResetEvent(false); 
    public AutoResetEvent killProcessingThread = new AutoResetEvent(false); 

    public MyClassOrForm() 
    { 
     myProcessingThread = new Thread(MyProcess); 
    } 

    private void MyProcess() 
    { 
     while (true) 
     { 
      if (startProcessing.WaitOne()) 
      { 
       // Do processing here 

       processingFinished.Set(); 
      } 

      if (killProcessingThread.WaitOne(0)) 
       return; 
     } 
    } 
} 

Luego, una vez que haya configurado los datos a procesar, llame forman otra clase o método

MyClassOrMethodInstance.startProcessing.Set(); 

Y si tiene que esperar a que el procesamiento para terminar a continuación, insertar:

MyClassOrMethodInstance.processingFinished.WaitOne(time_out_ms); 

Esto es equivalente a una llamada a Thread.Join(), solo que no tendrá que asignar otro hilo cada vez con los riesgos que implican los hilos si dependen de datos locales o hilo hijo sin terminar s.

1

Para un proyecto en el que estoy trabajando, creé un formulario que aparecerá, permanecerá abierto mientras la tarea se está ejecutando y se cerrará después.

Contiene una ProgressBar con los siguientes valores:

  • progressBar1.Style=ProgressBarStyles.Marquee
  • progressBar1.MarqueeAnimationSpeed = < - establece la velocidad a medida en milisegundos aquí

Si lo desea, puede establecer la forma de TopMost propiedad a true.

Aquí está el código para el formulario:

public partial class BusyForm : Form 
{ 
    public BusyForm(string text = "Busy performing action ...") 
    { 
     InitializeComponent(); 
     this.Text = text; 
     this.ControlBox = false; 
    } 

    public void Start() 
    { 
     System.Threading.Tasks.Task.Run(() => 
     { 
      this.ShowDialog(); 
     }); 
    } 

    public void Stop() 
    { 
     BeginInvoke((Action)delegate { this.Close(); }); 
    } 

    public void ChangeText(string newText) 
    { 
     BeginInvoke((Action)delegate { this.Text = newText; }); 
    } 
} 

Y aquí es el código para utilizar el formulario en el código:

 BusyForm busyForm = new BusyForm(text: "Opening database ..."); 

     busyForm.Start(); 

     //do your stuff here 

     busyForm.Stop(); 

ACTUALIZACIÓN: me encontré con algunas dificultades subyacentes con el enhebrado. Aquí hay una versión actualizada del código. Para obtener información de contexto, este formulario tiene una barra de progreso que se muestra cuando una tarea está ocupada. Agregué el comando ChangeText para mostrar un ejemplo de cómo puede interactuar con este formulario desde otro formulario.También debería mencionar que su Main en Program.cs debería tener el atributo [STAThread] como se ve a continuación.

[STAThread] 
    static void Main(string[] args) 
    { 
     System.Globalization.CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.InvariantCulture; 
     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Application.Run(new Form1()); 
    } 
Cuestiones relacionadas