Search code examples
c#pythonwinformsuser-interfaceno-response

How can WinForm host a long run IronPython script, and update UI for progress change


I need a WinForm act as the IronPython host, and during the IronPython run-time, the script can update UI to report its progress. I have the following code, and it does update the UI, but the problem is that the Window is not responsive. I appreciate any help.

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

        //use python
        private async void button1_Click(object sender, EventArgs e)
        {
            IProgress<ProgressReportInfo> progress = new Progress<ProgressReportInfo>(ReportProgress);
            await Task.Run(() =>
            {
                ScriptEngine engine = Python.CreateEngine();
                ScriptScope scope = engine.CreateScope();
                string script = "for i in range(1000000):\r\n";
                script += "   progress.Report(ProgressReportInfo(i / 10000, str(i / 10000)));\r\n";
                scope.SetVariable("progress", progress);
                scope.SetVariable("ProgressReportInfo", typeof(ProgressReportInfo));
                var code = engine.CreateScriptSourceFromString(script);
                code.Execute(scope);

            });

        }

        private void ReportProgress(ProgressReportInfo info)
        {
            progressBar1.Value = info.Percentage; 
            label1.Text = info.Status; 
        }

    }

    public class ProgressReportInfo
    {
        public ProgressReportInfo(int percentage, string status)
        {
            Percentage = percentage;
            Status = status;
        }
        public int Percentage { set; get; }
        public string Status { set; get; }
    }

}

Solution

  • You have to Invoke to UI thread:

        private void ReportProgress(ProgressReportInfo info)
        {
            // or better BeginInvoke
            Invoke(() =>
            {
                progressBar1.Value = info.Percentage;
                label1.Text = info.Status;
            });
        }
    

    and you don't need to await for Task.Run.

    Also, consider do not report every bit of progress change, but let say every 1000 changes.

    Other solution is to use polling watcher (your script change value of volatile variable, this value is checked in the timer)