-1

I have created a Windows Forms program that breaks down a file and sends its components to a server. These files are large, so I created a progressBar so users don't think it froze while the transactions are happening. What I would like to do is have some mechanism that will actively trigger only when all threads are complete without blocking the UI thread (again, so the plebs wont think its frozen). The best I could come up with is a kind of passive "wait until true" but I feel like there has to be a better way to do this. I have experimented with trying to create an event or a callback but honestly I've just ended up more confused than when I started. Here is an example of how I am doing this now:

    public partial class Program : Form
    {
        private readonly OpenFileDialog _ofd = new OpenFileDialog();
        public delegate void BarDelegate();
        private string _path;

        private void button1_Click(object sender, EventArgs e)
        {
            if (_ofd.ShowDialog() != DialogResult.OK) return;

            textBox1.Text = _ofd.SafeFileName;
            _path = _ofd.FileName;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            var allLinesFromFile = File.ReadAllLines(_path);
            progressBar1.Minimum = 0;
            progressBar1.Maximum = allLinesFromFile.Length;

            Task.Factory.StartNew(() => Parallel.ForEach(allLinesFromFile, DoSomething));

            while (progressBar1.Value < progressBar1.Maximum) //there has to be a better way to do this...
            {
                MessageBox.Show("Please wait.", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
            }

            //some processes here which should only take place after all threads are complete.

            var postingComplete = MessageBox.Show("The posting is complete!", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
            if (postingComplete == DialogResult.OK) Environment.Exit(0);
        }

        private void DoSomething(string record)
        {
            //some string manipulation and server transactions here

            BeginInvoke(new BarDelegate(() => progressBar1.Increment(1)));
        }
    }

3 Answers3

1

Try using Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms") for this. Then your code becomes:

    private void button2_Click(object sender, EventArgs e)
    {
        var allLinesFromFile = File.ReadAllLines(_path);
        progressBar1.Minimum = 0;
        progressBar1.Maximum = allLinesFromFile.Length;

        IDisposable subscription =
            allLinesFromFile
                .ToObservable()
                .SelectMany(f => Observable.Start(() => DoSomething(f)))
                .ObserveOn(this)
                .Do(x => progressBar1.Value += 1)
                .Subscribe(x => { }, () =>
                {
                    var postingComplete = MessageBox.Show("The posting is complete!", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                    if (postingComplete == DialogResult.OK)
                    {
                        Application.Exit();
                    }
                });
    }

    private void DoSomething(string record)
    {
        System.Threading.Thread.Sleep(5);
    }

If you need to stop this early then just call subscription.Dispose(). I've tested this and it works fine for me.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • I appreciate the answer but this appears to process the subscribe actions before all threads are complete. There are some calculations that should only take place after all threads are complete and this solution will allow the subscribe block to run when the last thread is fired but before it actually posts to the server. You can tell this because if you run this with a large file you will see the "Posting complete" message pop up before the bar reaches the end. This is the same problem I had when I tried to just add method calls to a ThreadPool, which is why I switched to Parallel.Foreach. – R. Devon North Sep 28 '16 at 18:48
  • Parallel.Foreach blocks execution until all threads complete, which is what I want, but unfortunately it also prevents the UI from being updated as well. Hence my dilemma. – R. Devon North Sep 28 '16 at 18:50
  • Added an idea for backgroundworker for you... Good luck! – Shannon Holsinger Sep 28 '16 at 20:13
  • @R.DevonNorth - "You can tell this because if you run this with a large file you will see the "Posting complete" message pop up before the bar reaches the end." - No, that's not true. The updates to the UI get pushed to the UI thread and they queue there. The processing absolutely finishes before the message box shows, but the UI thread is still busy animating the progress bar. It is finishing correctly. – Enigmativity Sep 29 '16 at 02:09
  • @Enigmativity You are correct. With this method the completion message appears about 1-2 seconds before the bar completes. I assumed that was a symptom of the problem I was having before with ThreadPool where the code after the loop was entered before all threads ran their course and the totals calculation would have incomplete info. However, when I compared the totals calculated between my old method and this method they came out the same. One question: why does this method have that gap when my while-loop method does not? As long as my totals are correct it matters little: just curious. – R. Devon North Sep 29 '16 at 17:11
0

You should be using the BackGroundWorker class, see: How to use a BackgroundWorker?

And use BackGroundWorker.RunWorkerComplete for when the thread has finished

Community
  • 1
  • 1
Jack Miller
  • 325
  • 1
  • 16
-1

Background worker:

 **Backgroundworker (System.ComponentModel)**

 BackgroundWorker loader = new BackgroundWorker();
    loader.DoWork += load_Specials_BW_Thread;
        loader.WorkerReportsProgress = true;
        loader.ProgressChanged += load_Special_Feeds_Progress_Changed;
    private void load_Specials_BW_Thread(object sender, DoWorkEventArgs e)
    {

        int pctComplete = (int)Math.Floor(ptComplete * 100);//recs done / total recs
        (sender as BackgroundWorker).ReportProgress(pctComplete);
    }

Good luck!

Shannon Holsinger
  • 2,293
  • 1
  • 15
  • 21