P.Brian.Mackey
P.Brian.Mackey

Reputation: 44275

Close loading form when all threads are complete

I want to know when all my async threads have completed so I know when to close my loading form. My code never closes the loading form. I don't know why. I'm unsure how to correctly pass my ManualResetEvent object to the async thread too.

I'm also open to a simpler means to achieve my goal of knowing when to close the loading form.

UPDATE

After reading the advice here I've updated my class. Unfortunetly, it still does not work. I feel closer though. It's just that the callback never fires.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;

namespace BrianTests
{

    public class TaskInfo
    {
        public RegisteredWaitHandle Handle;
        public string OtherInfo = "default";
        public Form loading;
    }


    public partial class AsyncControlCreateTest : Form
    {
        //List<ManualResetEvent> MREs = new List<ManualResetEvent>();
        Form loading = new Form() { Text = "Loading...", Width = 100, Height = 100 };
        CountdownWaitHandle cdwh;

        public AsyncControlCreateTest()
        {
            InitializeComponent();       
        }

        private void AsyncControlCreateTest_Load(object sender, EventArgs e)
        {            
            loading.Show(this);//I want to close when all the async threads have completed
            CreateControls();
        }  

        private void CreateControls()
        {
            int startPoint= 0;
            int threadCount = 2;
            cdwh = new CountdownWaitHandle(threadCount);

            for (int i = 0; i < threadCount; i++)
            {
                ManualResetEvent mre = new ManualResetEvent(initialState: true);                
                UserControl control = new UserControl() { Text = i.ToString() };                
                control.Load += new EventHandler(control_Load);
                Controls.Add(control);
                control.Top = startPoint;
                startPoint += control.Height;
                //MREs.Add(mre);
                //mre.Set();//just set here for testing
            }
            Task.Factory.StartNew(new Action(() => 
                {   
            TaskInfo info = new TaskInfo();
            info.loading = loading;
            try
            {
                info.Handle = ThreadPool.RegisterWaitForSingleObject(cdwh, WaitProc, info, 4000, executeOnlyOnce: false);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
                })); 
        }

        public static void WaitProc(object state, bool timedOut)
        {//this callback never occurs...
            TaskInfo ti = (TaskInfo)state;

            string cause = "TIMED OUT";
            if (!timedOut)
            {
                cause = "SIGNALED";
                // If the callback method executes because the WaitHandle is 
                // signaled, stop future execution of the callback method 
                // by unregistering the WaitHandle. 
                if (ti.Handle != null)
                    ti.Handle.Unregister(null);
            }

            Console.WriteLine("WaitProc( {0} ) executes on thread {1}; cause = {2}.",
                ti.OtherInfo,
                Thread.CurrentThread.GetHashCode().ToString(),
                cause
            );
            ti.loading.Close();
        }


        void control_Load(object sender, EventArgs e)
        {
            RichTextBox newRichTextBox = new RichTextBox();
            UserControl control = sender as UserControl;
            control.Controls.Add(newRichTextBox);            

            Task.Factory.StartNew(new Action(() => 
                {                    
                   Thread.Sleep(2000);
                   newRichTextBox.Invoke(new Action(() => newRichTextBox.Text = "loaded"));
                   cdwh.Signal();
                })); 
        }      
    }

    public class CountdownWaitHandle : WaitHandle
    {
        private int m_Count = 0;
        private ManualResetEvent m_Event = new ManualResetEvent(false);

        public CountdownWaitHandle(int initialCount)
        {
            m_Count = initialCount;
        }

        public void AddCount()
        {
            Interlocked.Increment(ref m_Count);
        }

        public void Signal()
        {
            if (Interlocked.Decrement(ref m_Count) == 0)
            {
                m_Event.Set();
            }
        }

        public override bool WaitOne()
        {
            return m_Event.WaitOne();
        }
    }
}

Upvotes: 1

Views: 809

Answers (2)

P.Brian.Mackey
P.Brian.Mackey

Reputation: 44275

Locking the MRE's and moving the WaitAll off the STA thread does the trick.

public partial class AsyncControlCreateTest : Form
{
    object locker = new object();
    static List<ManualResetEvent> MREs = new List<ManualResetEvent>();
    Form loading = new Form() { Text = "Loading...", Width = 100, Height = 100 };

    public AsyncControlCreateTest()
    {
        InitializeComponent();       
    }

    private void AsyncControlCreateTest_Load(object sender, EventArgs e)
    {            
        loading.Show(this);//I want to close when all the async threads have completed
        CreateControls();
    }  

    private void CreateControls()
    {
        int startPoint= 0;            
        for (int i = 0; i < 100; i++)
        {
            ManualResetEvent mre = new ManualResetEvent(initialState: false);                
            UserControl control = new UserControl() { Text = i.ToString() };                
            control.Load += new EventHandler(control_Load);
            Controls.Add(control);
            control.Top = startPoint;
            startPoint += control.Height;
            MREs.Add(mre);
        }
        Task.Factory.StartNew(new Action(() =>
        {
            try
            {
                WaitHandle.WaitAll(MREs.ToArray());
            }
            catch (Exception ex)
            {
                MessageBox.Show("error " + ex.Message);
            }
            finally
            {
                MessageBox.Show("MRE count = " + MREs.Count);//0 count provides confidence things are working...
                loading.Invoke(new Action( () => loading.Close()));
            }

        }));
    }

    void control_Load(object sender, EventArgs e)
    {
        RichTextBox newRichTextBox = new RichTextBox();
        UserControl control = sender as UserControl;
        control.Controls.Add(newRichTextBox);            

        Task.Factory.StartNew(new Action(() => 
            {                    
               Thread.Sleep(500);
               newRichTextBox.Invoke(new Action(() => newRichTextBox.Text = "loaded"));

               lock (locker)
               {
                   var ev = MREs.First();
                   MREs.Remove(ev);
                   ev.Set();
               }                   
            })); 
    }      
}

Upvotes: 0

Marcus
Marcus

Reputation: 6107

The problem is that WaitHandle.WaitAll is throwing an exception, which you can see:

 try
 {
    WaitHandle.WaitAll(MREs.ToArray());
 }
 catch (Exception e) {
    MessageBox.Show(e.Message);
    throw;
 }

The error message is that "WaitAll for multiple handles on a STA thread is not supported." If you do something like

foreach(var m in MREs)
   m.WaitOne();

It will work.

I'm not quite sure why the exception did not crash the application, as I would have hoped. See perhaps How can I get WinForms to stop silently ignoring unhandled exceptions? for this.

Upvotes: 1

Related Questions