8

I am still having problems with figuring out how to create winforms in a separate UI thread that I discussed here.

In trying to figure this out I wrote the following simple test program. I simply want it to open a form on a separate thread named "UI thread" and keep the thread running as long as the form is open while allowing the user to interact with the form (spinning is cheating). I understand why the below fails and the thread closes immediately but am not sure of what I should do to fix it.

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

namespace UIThreadMarshalling {
    static class Program {
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            var tt = new ThreadTest();
            ThreadStart ts = new ThreadStart(tt.StartUiThread);
            Thread t = new Thread(ts);
            t.Name = "UI Thread";
            t.Start();
            Thread.Sleep(new TimeSpan(0, 0, 10));
        }

    }

    public class ThreadTest {
        Form _form;
        public ThreadTest() {
        }

        public void StartUiThread() {
            _form = new Form1();
            _form.Show();
        }
    }
}
Community
  • 1
  • 1
George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • 1
    Yep, it's just Microsoft failing to properly think through anything other than a single dialog and single thread once again. As the solutions below say, you can start a separate message pump, but there is no convenient way to launch this from the GUI thread, hence there is no way to make the second form perform as a non-modal dialog. –  Jul 18 '12 at 23:24
  • To be fair, the question is from 4 years ago and concerning a 12 year-old technology :) – George Mauer Nov 20 '12 at 21:13
  • 1
    Yeah I know, but setting aside the fact that even 12 years ago people wanted this, without even looking I will bet a large sum that the situation hasn't changed in any revised/new API from MS. –  Nov 22 '12 at 04:22
  • I don't think that's fair, I never got too deep into WPF but what I did, didn't seem to suffer from these issues (in a lot of ways WPF was a more mature UI technology than HTML). The new javascript-based UIs I think work on a node-style event loop so I don't think the ui thread is even an issue. For all its many many faults, Microsoft does deserve some credit for how its dev tools have evolved. – George Mauer Nov 22 '12 at 23:18

6 Answers6

14

On a new thread, call Application.Run passing the form object, this will make the thread run its own message loop while the window is open.

Then you can call .Join on that thread to make your main thread wait until the UI thread has terminated, or use a similar trick to wait for that thread to complete.

Example:

public void StartUiThread()
{
    using (Form1 _form = new Form1())
    {
        Application.Run(_form);
    }
}
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
3
private void button1_Click(object sender, EventArgs e)
{
    var t = new Thread(RunNewForm);
    t.Start();
}
public static void RunNewForm()
{
     Application.Run(new Form2());
}
Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
CheeZe5
  • 975
  • 1
  • 8
  • 24
  • Although you probably wouldn't want to spawn a thread for each button click, this solution worked for me. – styfle Feb 05 '14 at 21:31
3

I think your problem is with this thought: "open a form on a separate thread named 'UI thread'"

The way windows works is like this (plz note Vista may change some of these realities):

There is one important thread called the "Main Thread" or the "UI Thread". This thread is the one that processes windows messages, like "hey the mouse clicked on this pixel."

These messages go into a queue, and the main thread processes them when it isn't busy doing something else.

So if you make a function call foo() on the main thread, if it takes a long time, no windows messages are processed during that time, and so no user interaction can occur.

The main thread also paints the UI on the screen, so long-running foo() will also stop your app from painting.

All other threads besides this holy and special main thread are grunt worker threads. These worker threads can do things, but they can never interact directly with the user interface.

This reality causes two problems:

  1. GETTING OFF THE MAIN THREAD: Since you don't want long-running foo() to halt all user interaction, you need to ship that work off to a worker thread.

  2. GETTING BACK TO THE MAIN THREAD: When long-running foo() completes, you probably want to notify the user by doing something in the UI, but you cannot do that in a worker thread, so you need to "get back" to the main thread.

So I believe your problem in the above program is very general: Your very goal is incorrect, because it is not supposed to be possible to call _form.Show() in any thread but the holy main thread.

rice
  • 1,063
  • 6
  • 17
2

You cannot open a GUI form in any thread, because it will be missing a message pump. You have to explicitly start a message pump in that thread by invoking Application.Run() in a thread method. Another option is to call a DoEvents() in a loop, if you need to do something else, because after Application.Run() that thread will wait a user to close a form in that point of execution.

Nenad Dobrilovic
  • 1,505
  • 1
  • 15
  • 29
1

I think just calling ShowDialog instead of Show will help. The problem seems to be that the thread finishes just after calling Show, after that the Form get's garbage collected. ShowDialog will halt the thread but still run form-events on it so the thread will keep running until the form is closed.

Normally i would do it the other way around. Run the form on the starting thread and start background threads when you want to start long-running background tasks.

I also read your other question but couldn't figure out what you're trying to do. MVP-architecture doesn't require you to run your business logic on different threads. Multi threading is hard to do right so I'd only use multiple threads if I really needed them.

Mendelt
  • 36,795
  • 6
  • 74
  • 97
1

Instead of calling show() on the form which will execute on the form and then just close at the end of the thread execution within function StartUiThread(), you can lock the thread until the form is stopped within the method as you are simply locking the other thread. Ex:

public void StartUiThread() {
        _form = new Form1();
        _form.ShowDialog(); //Change Show() to ShowDialog() to wait in thread
    }

This will cause the new thread to wait until the dialog is closed. I do not know if this will solve your problems, but it solved mine.

Oli4
  • 489
  • 1
  • 9
  • 22