Search code examples
c#windows-phone-7windows-phonebackgroundworkerironpython

Run IronPython Async while writing to textbox


I want to know how I can run a very long running python script asynchronously so that it does not block my entire application but I also want its stream output to be written to a textbox while it's running. I have managed to figure out the first part but not the second. Currently the textbox only gets updated after the script has finished. Here is the important parts of my code:

MainPage.xaml.cs:

...
BoxStream bs = null;

public MainPage()
    {
        InitializeComponent();

        bs = new BoxStream(OutBox);
        ...
    }
...
private bool slice()
    {
        try
        {
            var ipy = Python.CreateEngine();
            var iscp = ipy.CreateScope();
            iscp.SetVariable("rootP", Directory.GetCurrentDirectory());
            ipy.ExecuteFile("Pathsetter.py", iscp);
            var ipath = ipy.GetSysModule().GetVariable("path");

            ScriptEngine m_engine = Python.CreateEngine();
            ScriptScope m_scope = m_engine.CreateScope();
            m_engine.GetSysModule().SetVariable("path", ipath);

            m_engine.Runtime.IO.SetOutput(bs, Encoding.UTF8);
            m_engine.Runtime.IO.SetErrorOutput(bs, Encoding.UTF8);

            /*string dir = Directory.GetCurrentDirectory();
            string ndir = dir.Replace(@"\", @"\\");*/

            m_engine.ExecuteFile(Directory.GetCurrentDirectory() + "\\Skeinforge\\skeinforge_application\\skeinforge_utilities\\skeinforge_craft.py", m_scope);

            string stl_path = Directory.GetCurrentDirectory() + "\\test.stl";
            object func = m_scope.GetVariable("makeGcode");
            object result = m_engine.Operations.Invoke(func, stl_path);

            Debug.WriteLine(result); 
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Py error: " + ex.Message);
            return false;
        }

        return true;
    }
...
private void Slice_Button_Click(object sender, RoutedEventArgs e)
    {
        BackgroundWorker bw = new BackgroundWorker();
        bw.DoWork += new DoWorkEventHandler(
        delegate(object o, DoWorkEventArgs args)
        {
            BackgroundWorker b = o as BackgroundWorker;

            slice();
        });
    }
...

BoxStream.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using System.Windows.Controls;

namespace SkeinforgeWP8
{
    class BoxStream : Stream
    {
        TextBox _OutBox = null;

        public BoxStream(TextBox tBox)
        {
            Debug.WriteLine("Writing stream to box");
            _OutBox = tBox;
            _OutBox.Text = "";
        }

        public override void WriteByte(byte value)
        {
            base.WriteByte(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            _OutBox.Text += Encoding.UTF8.GetString(buffer, offset, count);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void Flush()
        {
            return;
        }

        public override long Position
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public override long Length
        {
            get { throw new NotImplementedException(); }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanRead
        {
            get { return false; }
        }
    }
}

I guess this is probably rather simple but after a lot of searching all I could find was information telling me to use BackgroundWorker as I did above, this does not work though because my textbox does not get live updates.


Solution

  • Since the TextBox update will be coming in on a background thread it will fail. If you wrap your TextBox update with a call to the Dispatcher it will run the code on the next UI thread tick and should update correctly:

        public override void Write(byte[] buffer, int offset, int count)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() => {
                _OutBox.Text += Encoding.UTF8.GetString(buffer, offset, count);
            });
        }