-2

I am developing a plugin for an application that allows additional forms to be created. Some of the plugins are strictly "run-it-and-forget-it" workers, but some have a UI for further control. The plugin exposes an API (via .dll) that shall be subclassed (via their Core class). It has a method that initializes the plugin, as well as a method that shuts down the plugin.

What I'm wanting to do is have the worker thread on the main thread, and the UI thread on another thread that I can send events to as well as receive control events from (start, stop, etc.)

My current implementation has the UI successfully updating; unfortunately, BeginInvoke is of no use because the form is being initialized on my main thread even though it is instantiated and shown in the child thread.

Why is this causing the GUI thread to be blocked when I call form1.UpdateScanningProgress(i)? It appears that this.InvokeRequired is always false in the example below, even though I'm running the form in another thread.

Here is the "main" code. Note that the plugin information has been stripped. I'm wanting to be as basic as possible:

namespace TestBed
{
    class TestBedManager : PluginManager
    {
        Form1 form1;

        void UIInitialized(object sender, EventArgs e)
        {

        }

        void BeginScan(object sender, EventArgs e)
        {
            for (int i = 0; i <= 100; ++i)
            {
                Thread.Sleep(150);
                form1.UpdateScanningProgess(i);
            }

            Thread.Sleep(1000);
        }

        // Parent calls when plugin starts
        public void PluginStart()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            form1 = new Form1();
            form1.Initialized += Initialized;
            form1.BeginScan += BeginScan;

            Thread thread = new Thread(() =>
            {
                Application.Run(form1);
            });

            thread.Start();

            // Here is where I'd start my real work instead of simply joining
            thread.Join();
        }

        // Parent calls when plugin needs to stop
        public void PluginStop()
        {
        }
    }
}

Here is the Form code:

namespace TestBed
{
    public partial class Form1 : Form
    {
        public event EventHandler Initialized;
        public event EventHandler BeginScan;

        public Form1()
        {
            InitializeComponent();
        }

        delegate void UpdateScanningProgressDelegate(int progressPercentage);

        public void UpdateScanningProgress(int progressPercentage)
        {
            if (this.InvokeRequired)
            {
                UpdateScanningProgressDelegate updateScanningProgress = new UpdateScanningProgressDelegate(UpdateScanningProgress);
                updateScanningProgress.BeginInvoke(progressPercentage, null, null);
            }
            else
            {
                this.scanProgressBar.Value = progressPercentage;
            }
        }

        delegate void StopScanningDelegate();

        public void StopScanning()
        {
            if (this.InvokeRequired)
            {
                StopScanningDelegate stopScanning = new StopScanningDelegate(StopScanning);
                stopScanning.BeginInvoke(null, null);
            }
            else
            {
                this.scanProgressBar.Value = 0;
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            if (Initialized != null)
                Initialized(this, EventArgs.Empty);
        }

        private void scanButton_Click(object sender, EventArgs e)
        {
            if (BeginScan != null)
                BeginScan(this, EventArgs.Empty);
        }
    }
}

I feel that I'm probably missing a small detail and would appreciate an extra pair of eyes.

Parazuce
  • 157
  • 1
  • 10
  • 1
    Why do this? You've already got a perfectly good STA thread where your GUI is already running. Why abuse it by tying it up with some long-running process while you create a different thread to take over handling of the GUI? Note that Control.Invoke/BeginInvoke works fine as long as you use the correct Control instance to call the method; if you want the target delegate invoked on your new UI thread, you need to use a Control instance that is owned by that thread, not your main UI Form instance. – Peter Duniho Oct 17 '14 at 01:12
  • I updated the code. The main thread does not always have a GUI and it also subscribes to many events that the PluginManager offers. – Parazuce Oct 17 '14 at 01:30
  • 2
    Two are at least two critical flaws in your question: 1) there's no question. You intimate that there's some bug at work, but there's no specific description of what you expect to happen, nor of what happens that is different from that. And 2), the code example is incomplete and full of stuff that no one else has access to, when it should be a concise-but-complete code example that reliably reproduces the problem. – Peter Duniho Oct 17 '14 at 01:39
  • Updated with the question. I'm curious why the GUI thread is being blocked when I'm performing a BeginInvoke behind the hood. – Parazuce Oct 17 '14 at 01:46
  • BeginInvoke will still block on the UI thread it will just not block on the calling thread. http://stackoverflow.com/questions/229554/whats-the-difference-between-invoke-and-begininvoke – Brian Oct 17 '14 at 02:19
  • Many problems with this code. Chief one you are complaining about is BeginScan(). It makes the UI go catatonic for 16 seconds since it runs on the same thread that your form is running on. Don't do this, many more ways to shoot your leg off. – Hans Passant Oct 17 '14 at 08:04

1 Answers1

1

I'm skeptical that "this.InvokeRequired" is actually returning false. But in any case, you are calling the wrong "BeginInvoke" method.

You should be calling Control.Invoke or Control.BeginInvoke. But instead, you are calling the compiler-generated Delegate.BeginInvoke method.

The Control.Invoke/BeginInvoke methods cause a given delegate instance to be invoked on the control's owning thread. The Delegate.BeginInvoke method simply queues invocation of the delegate to the ThreadPool.

The way the code's written now, you're going to get into an endless cycle of invoking the UpdateScanningProgress method and at no point ever get around to actually updating the progress UI. Probably the GUI thread isn't getting blocked at all…it's just never asked to do anything interesting.

Personally, I don't use "InvokeRequired" at all. It's harmless to use Invoke or even BeginInvoke when it returns false, and the code's simpler without it. So I always just call Invoke/BeginInvoke directly.

Try this:

public void UpdateScanningProgress(int progressPercentage)
{
    this.BeginInvoke((MethodInvoker)(() => this.scanProgressBar.Value = progressPercentage));
}
Peter Duniho
  • 62,751
  • 5
  • 84
  • 120