0

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).

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • For me it looks like you are using `Async` function on BackgroundThreadWorker - it doesn't mix well with situation that you stop message pump (I mean that you are blocking main thread in app waiting on `WaitOne`). – csharpfolk May 27 '16 at 18:35
  • @csharpfolk What is the alternative? If I remove the WaitOne call, how can the form do what I am trying to achieve? Can the worker when it completes inform the parent somehow? – Andrew Truckle May 27 '16 at 18:39
  • 1
    After a bit of search the problem is slightly different, BackgroundWorker will try to fire events on UI thread http://stackoverflow.com/questions/2806814/backgroundworker-runworkercompleted-event that you are blocking waitin on `WaitOne` – csharpfolk May 27 '16 at 18:39
  • @csharpfolk OK, so if I remove that WaitOne, do we have another way to proceed? – Andrew Truckle May 27 '16 at 18:40
  • you may add custom event to your worker class and invoke it on OnCompleted event from worker. – csharpfolk May 27 '16 at 18:41
  • @csharpfolk I would be grateful if you can show me, with an answer ... :) – Andrew Truckle May 27 '16 at 18:44
  • This is the job of your RunWorkerCompleted event handler method. Move whatever code you now have after the WaitOne() call into that method. And now you can delete that MRE since it is not needed anymore. – Hans Passant May 27 '16 at 18:51
  • @HansPassant, in this case I can't because the form has another object that must do something. If I pass that object into the worker and do the work there it fails but if I do the work in the form it does not fail. Kind of going off at a tangent. Is there a way to work with a form member variable from the thread complete without passing it in? – Andrew Truckle May 27 '16 at 18:54
  • "It fails" is a rather hopeless diagnostic but surely it failed because you used it on the wrong thread, not because you used it in another method or class. Simplest way to use a form member is to write the event handler as a form member as well. – Hans Passant May 27 '16 at 18:57

1 Answers1

3

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

Community
  • 1
  • 1
csharpfolk
  • 4,124
  • 25
  • 31
  • Exactly. No need for a `ManualResetEvent` here. The `OnCompleted` handler does what he needs. – Jim Mischel May 27 '16 at 19:44
  • I am getting an error in the real file: CS0136 A local or parameter named 'sender' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter GENIO Viewer D:\My Programs\GENIO Viewer\GENIO Viewer\GENIO_Viewer_Form.cs 125 Active Error CS0136 A local or parameter named 'e' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter GENIO Viewer D:\My Programs\GENIO Viewer\GENIO Viewer\GENIO_Viewer_Form.cs 125 Active – Andrew Truckle May 27 '16 at 19:46
  • Name collision - sorry, I fixed that – csharpfolk May 27 '16 at 19:52
  • Thank you! I have been able to adjust it in my real class and apply the code that I wanted after the thread finished. All good. :) – Andrew Truckle May 27 '16 at 19:58