Search code examples
c#backgroundworkermanualresetevent

ManualResetEvent is not working as expected - Form is hanging


I have written a class that uses a worker thread and it utilises an event object. I have cut the class down to the basics:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace TEST
{
    public class TEST_Worker
    {
        public static ManualResetEvent m_Event = new ManualResetEvent(false);
        private BackgroundWorker m_backgroundWorker;

        public TEST_Worker()
        {
            InitBackgroundWorker();
        }

        private void InitBackgroundWorker()
        {
            m_backgroundWorker = new BackgroundWorker();
            m_backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
            m_backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
            m_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
            m_backgroundWorker.WorkerReportsProgress = true;
        }

        public void start()
        {
            m_Event.Reset();
            m_backgroundWorker.RunWorkerAsync();
        }

        public void stop()
        {
            m_backgroundWorker.CancelAsync();
        }

        public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            m_backgroundWorker.ReportProgress(100, "Progress {0}%");
        }

        // This event handler updates the UI
        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {

        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            m_Event.Set();
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // dispose managed resources
                m_backgroundWorker.Dispose();
            }
            // free native resources
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

In the form class (a menu item handler) I am doing this:

private void testToolStripMenuItem_Click(object sender, EventArgs e)
{
    TEST.TEST_Worker myTest = new TEST.TEST_Worker();

    myTest.start();
    TEST.TEST_Worker.m_Event.WaitOne();

    MessageBox.Show("Complete!");
}

I don't have it right. The form needs to not sleep or anything (else the GUI will not get updated - real code does progress bar updates). But, I also want the form to know when the thread has finished and then perform a task. At the moment it just carries on after the call to start.

Clearly I am not using the ManualResetEvent in the right way. At the moment my application just hangs indefinitely. It never shows the message box (I don't want the thread to show it - but the form).

Does this make sense? Am I going about this the wrong way?

I have used it before, but admittedly it was when a worker thread was required to wait for a sub-worker thread to finish before it carried on. This context is different, a form calling a worker thread and then wanting to do something when the thread has finished (but not choke the form for GUI updates).


Solution

  • As stated in comment, you may want to try raising custom event from class that wraps Background Worker:

    public class TEST_Worker 
    {
        public event EventHandler OnCompleted = delegate { };
    
        // ....
    
        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnCompleted(this, EventArgs.Empty);
        }
    }
    
    // In form:
    
    private void testToolStripMenuItem_Click(object sender, EventArgs e)
    {
        TEST.TEST_Worker myTest = new TEST.TEST_Worker();
    
        myTest.OnCompleted += (_sender, _e) => {
            MessageBox.Show("Complete!");
        };
    
        myTest.start();
    }
    

    The other way around is to use raw thread and sync for access form via Invoke as described here: Accessing a form's control from a separate thread