We help IT Professionals succeed at work.

C# Await issue

whorsfall
whorsfall asked
on
Hi,

I am doing some testing with Await in WinForms.

What I am puzzled at the behaviour is why when the code
runs the progress bar does not stay in sync with the textbox.

Please see my screen shot plus my code,

What am i doing wrong and why is it behaving the way it is?

Thanks,

Ward.

Screen shot of issue.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Winforms_Task_Test1
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private async void button_Go_Click(object sender, EventArgs e)
        {
            TestClass item = new TestClass();

            int limit = 10;

            item.Init(textBox_Status, progressBar_Progress, limit, 100);

            for (int i=1;i<=limit;i++)
            {
                await item.Process(i);
            }

        }

        private void button_Exit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

    }
}

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

namespace Winforms_Task_Test1
{
    class TestClass
    {
        public Control Text_control;
        public ProgressBar Progress_control;
        public int delay;
        public int max_value;
        public void SetControlThreadSafe(Control control, Action<object[]> action, object[] args)
        {
            if (control.InvokeRequired)
            {
                try
                {
                    control.Invoke(new Action<Control, Action<object[]>, object[]>(SetControlThreadSafe), control, action, args);
                }
                catch
                {
                }
            }
            else action(args);
        }

        public void Init(Control textbox_control, ProgressBar progressbar_control, int limit, int delay_ms)
        {
            Text_control = textbox_control;
            Progress_control = progressbar_control;

            SetControlThreadSafe(Text_control, (arg) =>
            {
                Progress_control.Value = 0;
                Progress_control.Maximum = limit;
                // Progress_control.Refresh();
            }, null);

            SetControlThreadSafe(Text_control, (arg) =>
            {
                Text_control.Text = "";
                // Text_control.Refresh();
            }, null);

            delay = delay_ms;
            max_value = limit;
        }

        public async Task Process(int counter)
        {
            SetControlThreadSafe(Progress_control, (arg) =>
            {
                Progress_control.Value = counter;
                Progress_control.Refresh();
                }, null);

            SetControlThreadSafe(Text_control, (arg) =>
            {
                Text_control.Text = string.Format("Counter: {0}", counter);
                Text_control.Refresh();
                }, null);


            await Task.Delay(delay);

        }

    }


}

Open in new window

Comment
Watch Question

Developer
Distinguished Expert 2019
Commented:
It's because of a little known rendering bug with the animation on the progress bar control:

https://stackoverflow.com/questions/6071626/progressbar-is-slow-in-windows-forms

This should work for you:

Form1.cs -
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace EE_Q29168562
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void OnClick(object sender, EventArgs e)
        {
            var btn = sender as Button;
            if (Equals(btn, btnExit))
            {
                Application.Exit();
            }
            else if (Equals(btn, btnGo))
            {
                var values = Enumerable.Range(1, 10).ToList();
                var process = new TestClass(tbCounter, pbCounter, values.Count(), 100);
                foreach (var v in values)
                {
                    await process.Process(v);
                }
            }
        }
    }

    class TestClass
    {
        private int delay;
        private int maxValue;

        public Control TextControl { get; }
        public ProgressBar ProgressControl { get; }

        public TestClass(Control textControl, ProgressBar progressControl, int limit, int delay)
        {
            TextControl = textControl;
            ProgressControl = progressControl;
            maxValue = limit;
            this.delay = delay;

            Initialize();
        }

        private void Initialize()
        {
            TextControl.HandleInvokeRequired(c => c.Text = "");
            ProgressControl.HandleInvokeRequired(c => 
            {
                c.Value = 0;
                c.Maximum = maxValue;
            });
        }

        public async Task Process(int counter)
        {
            TextControl.HandleInvokeRequired(c => c.Text = $"Counter: {counter}");
            ProgressControl.HandleInvokeRequired(c => c.SetProgressNoAnimation(counter));

            await Task.Delay(delay);
        }
    }

    static class Extensions
    {
        public static void HandleInvokeRequired<T>(this T control, Action<T> action) where T : Control, ISynchronizeInvoke
        {
            if (control == null)
            {
                throw new ArgumentNullException($"Cannot execute {action} on Control.  Control is null.");
            }

            if (control is Control && (control as Control).IsDisposed)
            {
                throw new ObjectDisposedException($"Cannot execute {action} on Control.  Control is disposed.");
            }

            if (control is Control && !(control as Control).IsHandleCreated)
            {
                throw new InvalidOperationException($"Cannot execute {action} on {control}.  Handle is not created for {control}.");
            }

            if (control.InvokeRequired)
            {
                try
                {
                    control.Invoke(action, new object[] { control });
                }
                catch (Exception ex)
                {
                    throw new Exception($"Cannot execute {action} on {control}.  {ex.Message}.");
                }
            }
            else
            {
                try
                {
                    action(control);
                }
                catch (Exception ex)
                {
                    throw new Exception($"Cannot execute {action} on {control}.  {ex.Message}.");
                }
            }
        }

        public static void SetProgressNoAnimation(this ProgressBar pb, int value)
        {
            if (Equals(value, pb.Maximum))
            {
                pb.Maximum = value + 1;
                pb.Value = value + 1;
                pb.Maximum = value;
            }
            else
            {
                pb.Value = value + 1;
            }
            pb.Value = value;
        }
    }
}

Open in new window

Form1.Designer.cs -
namespace EE_Q29168562
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.tbCounter = new System.Windows.Forms.TextBox();
            this.pbCounter = new System.Windows.Forms.ProgressBar();
            this.btnGo = new System.Windows.Forms.Button();
            this.btnExit = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // tbCounter
            // 
            this.tbCounter.Location = new System.Drawing.Point(12, 12);
            this.tbCounter.Name = "tbCounter";
            this.tbCounter.Size = new System.Drawing.Size(329, 20);
            this.tbCounter.TabIndex = 0;
            // 
            // pbCounter
            // 
            this.pbCounter.Location = new System.Drawing.Point(12, 38);
            this.pbCounter.Name = "pbCounter";
            this.pbCounter.Size = new System.Drawing.Size(329, 23);
            this.pbCounter.TabIndex = 1;
            // 
            // btnGo
            // 
            this.btnGo.Location = new System.Drawing.Point(12, 67);
            this.btnGo.Name = "btnGo";
            this.btnGo.Size = new System.Drawing.Size(75, 23);
            this.btnGo.TabIndex = 2;
            this.btnGo.Text = "Go";
            this.btnGo.UseVisualStyleBackColor = true;
            this.btnGo.Click += new System.EventHandler(this.OnClick);
            // 
            // btnExit
            // 
            this.btnExit.DialogResult = System.Windows.Forms.DialogResult.Cancel;
            this.btnExit.Location = new System.Drawing.Point(266, 67);
            this.btnExit.Name = "btnExit";
            this.btnExit.Size = new System.Drawing.Size(75, 23);
            this.btnExit.TabIndex = 3;
            this.btnExit.Text = "Exit";
            this.btnExit.UseVisualStyleBackColor = true;
            this.btnExit.Click += new System.EventHandler(this.OnClick);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(354, 96);
            this.Controls.Add(this.btnExit);
            this.Controls.Add(this.btnGo);
            this.Controls.Add(this.pbCounter);
            this.Controls.Add(this.tbCounter);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.TextBox tbCounter;
        private System.Windows.Forms.ProgressBar pbCounter;
        private System.Windows.Forms.Button btnGo;
        private System.Windows.Forms.Button btnExit;
    }
}

Open in new window

HTH,

-saige-