开发者

Spawn a new thread to open a new window and close it from a different thread

Right now I have C# code to spawn a new window in a different thread, this works, but as soon as the new spawned window opens开发者_运维问答, it closes and the thread ends. How would I make it so the new spawned window can be closed from the first thread?

Here is a "tree" of how the spawning currently works:

Main thread

--Uses a function in the main thread to start another function in a separate thread to open w window, causing the window to use that thread.

Basically I just want the two windows to each have their own thread. And be able to control the spawned secondary window from the first window thread.


I bet what you're doing is something like this:

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

because this makes the window disappear immediately, like you describe.

Try this instead:

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

ShowDialog spins its own message pump, and only returns when the window is closed.


This is just a quick example. It's a little more robust than the first one I wrote. It eliminates the existing race condition by using p/invoke.

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


You can do it like this:

In 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();
        }
    }
}

In 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();
        }
    }
}

And 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();
        }
    }
}

This way you can get two forms on the same thread and use one to control the other one. If you need to use threads I would suggest to use dedicated threads that are part of the classes an not spawn in a method that can be called more than once. Then use ManualResetEvent or AutoResetEvent to control the thread processing. I really like the approach of using something like this, because is safe and doesn't spend much resources initialising threads.

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

Then, once you have set the data to be processed, call form another class or method

MyClassOrMethodInstance.startProcessing.Set();

And if you need to wait for that processing to finish then insert:

MyClassOrMethodInstance.processingFinished.WaitOne(time_out_ms);

This is equivalent to a Thread.Join() call, only that you wont have to allocate another thread every time with the risks that threads entails if they depend on local data or unfinished child threads.


I'm writing an application that is threaded and uses the UI on the created thread to dispatch drawing functionality to the DC.

When we ported the application to run from command prompt we were naturally left with a bit of a problem as the dispatcher thread wasn't created or needed - so I created another thread from the app entry point that essentially called ShowDialog() (the only way to spin the message pump) on the main form - with an overidden OnShown to permanently hide and minimize the form when the call is made. This allowed me to still dispatch to the form and handle all of my drawing from the other multiple threads.

It's certainly an ugly approach, but this was a quick way to get it done, and it works as expected.


For a project I'm working on I created a form that'll pop up, stay open while the task is running and close afterwards.

It contains one ProgressBar with the following settings:

  • progressBar1.Style=ProgressBarStyles.Marquee
  • progressBar1.MarqueeAnimationSpeed = <-- set your custom speed in miliseconds here

If you want to, you can set the form's TopMost property to true.

Here is the code for the form:

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

And here is the code to use the form in your code:

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

        busyForm.Start();

        //do your stuff here

        busyForm.Stop();

UPDATE: I ran into some underlying troubles with the threading. Here is an updated version of the code. For some background info, this form has a progress bar that is shown when a task is busy. I added the ChangeText command to show an example of how you can interact with this form from another form. Should probably also mention that your Main in Program.cs should have the [STAThread] attribute as seen below.

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜