Kill KRT
Kill KRT

Reputation: 1261

Updating UI from Parallel.ForEach

I am just trying to update a progress bar in Parallel.ForEach and the application seems to freeze.

Despite I have already tried to use Invoke solution as suggested on other posts, I still have problems.

This is a simplified version of my code:

public partial class Form1 : Form
{
    event EventHandler ReportProgress;

    class ProgressEvent : EventArgs
    {
        public int Total { get; set; }
        public int Current { get; set; }
    }

    public Form1()
    {
        InitializeComponent();
        ReportProgress += Form1_ReportProgress;
    }

    private void Form1_ReportProgress( object sender, EventArgs e )
    {
        var progress = e as ProgressEvent;
        if ( InvokeRequired )
        {
            this.Invoke( new Action( () => { progressBar.Maximum = progress.Total; progressBar.Value = progress.Current; } ) );
        }
    }

    private void buttonStart_Click( object sender, EventArgs e )
    {
        // Create a list with seed to be used later
        List<int> values = new List<int>() { };
        values.AddRange( Enumerable.Range( 1, 15 ) );

        var total = values.Count();
        var current = 0;

        Parallel.ForEach( values, v =>
        {
            Interlocked.Increment( ref current );

            var r = new Random( v );
            // Just sleep a little
            var sleep = r.Next( 10, 1000 );
            Thread.Sleep( sleep );

            // Update the progress bar
            ReportProgress( null, new ProgressEvent() { Current = current, Total = total } );
        }
        );

        MessageBox.Show( "Done " );
    }
}

The application seems to hang and the Message box is not shown, while if I remove the event generation (ReportProgress) everything works perfectly, but obviously the progress bar is not update at all.

Upvotes: 0

Views: 3069

Answers (2)

paul
paul

Reputation: 22001

You could change:

this.Invoke( new Action( () => { progressBar.Maximum = progress.Total; progressBar.Value = progress.Current; } ) );

to:

this.BeginInvoke( new Action( () => { progressBar.Maximum = progress.Total; progressBar.Value = progress.Current; } ) );

This will execute the action on an async thread.

Upvotes: 1

Adem Catamak
Adem Catamak

Reputation: 2009

I edit your code. If you want to screen do not freeze, probably easy way is using background worker for performing operation.

When you use background worker, UI thread is not blocked.Hence, user still can interact with UI.

public partial class Form1 : Form
{
    event EventHandler ReportProgress;

    class ProgressEvent : EventArgs
    {
        public int Total { get; set; }
        public int Current { get; set; }
    }

    public Form1()
    {
        InitializeComponent();
        ReportProgress += Form1_ReportProgress;
    }

    private void Form1_ReportProgress(object sender, EventArgs e)
    {
        var progress = e as ProgressEvent;
        this.Invoke(new Action(() =>
                               {
                                   progressBar.Maximum = progress.Total;
                                   progressBar.Value = progress.Current;
                               }));
    }

    private void button1_Click(object sender, EventArgs e)
    {
        // Collect data from UI, I use list for example
        List<int> values = new List<int>() { };
        values.AddRange(Enumerable.Range(1, 100));

        BackgroundWorker backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += BackgroundWorkerOnDoWork;
        backgroundWorker.RunWorkerAsync(values);
    }

    private void BackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
    {
        var values = (IEnumerable<int>) doWorkEventArgs.Argument; // the 'argument' parameter resurfaces here

        var total = values.Count();
        var current = 0;

        Parallel.ForEach(values, v =>
                                 {
                                     Interlocked.Increment(ref current);

                                     var r = new Random(v);
                                     // Just sleep a little
                                     var sleep = r.Next(10, 1000);
                                     Thread.Sleep(sleep);

                                     // Update the progress bar
                                     ReportProgress(null, new ProgressEvent() {Current = current, Total = total});
                                 }
                        );

        MessageBox.Show("Done ");
    }
}

Upvotes: 1

Related Questions