简体   繁体   中英

How do I make my winforms UI update quicker?

I have two Intel RealSense Cameras capturing and recording data. I am capturing frames for awhile and then taking the data and updating the UI with it, but it comes in spurts. I want to have a seemingly continuous flow of data coming in.

I've tried by making an asynchronous event that goes through the frames and updates as seen below:

private async void ButtonStart_Click(object sender, EventArgs e)
{
            ButtonStart.Enabled = false;
            try
            {
                Context context = new Context();
                // var dev = context.QueryDevices();
                //Declare RealSense pipeline, encapsulating the actual device and sensors
                /*
                Pipeline pipe = new Pipeline();
                //create a config for the pipeline
                var cfg = new Config();
                //add the T265 Pose stream
                cfg.EnableStream(Stream.Pose);
                //start the stream
                var pp = pipe.Start(cfg);
                */
                Config config = new Config();
                var dev = ctx.QueryDevices();
                int i = 0;
                foreach (var device in dev)
                {
                    config.EnableDevice(dev[i].Info[CameraInfo.SerialNumber]);
                    config.EnableAllStreams();
                    Pipeline p = new Pipeline(ctx);
                    p.Start(config);
                    pipelines[i] = p;
                    i++;
                }
                //Pipeline pipe2 = new Pipeline();
                //var cfg2 = new Config();
                //cfg2.EnableStream(Stream.Color);
                //cfg2.EnableStream(Stream.Depth);
                // var pp2 = pipe2.Start(cfg2);
                //variables for direction change
                float lastX = -300;
                float lastY = -300;
                float lastZ = -300;
                string dx = "";
                string dy = "";
                string dz = "";
                //main loop for frames
                await Task.Run(() =>
                {
                    while (true)
                    {
                        using (var frames = pipelines[1].WaitForFrames())
                        {
                            var f = frames.FirstOrDefault(Stream.Pose);
                            var pose_data = f.As<PoseFrame>().PoseData;

                            if (lastX != -300)
                            {
                                //x
                                if (lastX > pose_data.translation.x)
                                {
                                    dx = "LEFT";
                                }
                                else if (lastX < pose_data.translation.x)
                                {
                                    dx = "RIGHT";
                                }
                                else
                                {
                                    dx = "";
                                }

                                //y
                                if (lastY > pose_data.translation.y)
                                {
                                    dy = "DOWN";
                                }
                                else if (lastY < pose_data.translation.y)
                                {
                                    dy = "UP";
                                }
                                else
                                {
                                    dy = "";
                                }
                                //z
                                if (lastZ > pose_data.translation.z)
                                {
                                    dz = "FORWARD";
                                }
                                else if (lastZ < pose_data.translation.z)
                                {
                                    dz = "BACKWARD";
                                }
                                else
                                {
                                    dz = "";
                                }
                            }
                            //update the UI
                            UpdateUI(pose_data.translation.x.ToString(), pose_data.translation.y.ToString(), pose_data.translation.z.ToString(), dx, dy, dz);                         
                            //Set last x,y,z to see what change occurred
                            lastX = pose_data.translation.x;
                            lastY = pose_data.translation.y;
                            lastZ = pose_data.translation.z;
                            dx = "";
                            dy = "";
                            dz = "";
                        }
                    }
                });                
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            } //end catch
            ButtonStart.Enabled = true;
}


public void UpdateUI(string x, string y, string z, string dx, string dy, string dz)
{
            var timeNow = DateTime.Now;

            if ((DateTime.Now - previousTime).Milliseconds <= 15) return;
            //object o = new object();
            //label for x
            synchronizationContext.Post(new SendOrPostCallback(o =>
            {

                LabelXPosition.Text = o.ToString();

            }), x);
            //label for y
            synchronizationContext.Post(new SendOrPostCallback(o =>
            {
                //Set the labels for x,y,z 
                LabelYPosition.Text = o.ToString();

            }), y);
            //label for z
            synchronizationContext.Post(new SendOrPostCallback(o =>
            {
                //Set the labels for x,y,z 
                LabelZPosition.Text = o.ToString();

            }), z);
            //label for dx
            synchronizationContext.Post(new SendOrPostCallback(o =>
            {
                //Set the labels for x,y,z 
                LabelLR.Text = o.ToString();

            }), dx);
            //label for dy
            synchronizationContext.Post(new SendOrPostCallback(o =>
            {
                //Set the labels for x,y,z 
                LabelUD.Text = o.ToString();

            }), dy);
            //label for dz
            synchronizationContext.Post(new SendOrPostCallback(o =>
            {
                //Set the labels for x,y,z 
                LabelFB.Text = o.ToString();

            }), dz);

            previousTime = timeNow;
}

Is there any way I can stop it from "freezing" up while reporting the data?

Device initialization

If device initialization does not need synchronization context then just include entire try body into a task:

private async void ButtonStart_Click(object sender, EventArgs e) {
  ButtonStart.Enabled = false;
  try 
  {
    await Task.Run(() => {
      Context context = new Context();
      Config config = new Config();
    ...
    }
  } catch {
  ...
  }
}

Initialization will be dispatched in the thread pool thread and UI won't freeze. Please note that creating methods that are async and return void is a bad practice , event handler is an exception though.

UI dispatch

Inside of UpdateUI you dispatch 6 messages to synchronization context to update 6 labels. You can group everything to a single update:

synchronizationContext.Post(_ => { 
  LabelXPosition.Text = x.ToString();
  LabelYPosition.Text = y.ToString()
  ...
}, null));

There is a small overhead on capturing your variable to closure, however, you have less events dispatched in the UI thread and also you don't box all of them by casting to an object in Post method (second argument).

DateTime.Now

DateTime.Now is rather expensive operation as it converts time to current timezone. DateTime.UtcNow is more lightweight. However, for your purposes I think Stopwatch will be much more suitable.

Throttling inside UpdateUI

There are 2 concerns here: too small time and functional issue in the algorithm.

You don't update UI if time between frames is less than 15ms:

if ((DateTime.Now - previousTime).Milliseconds <= 15) return;

Please note, that 15ms is a rather small value and I bet none user will be able to handle such refresh rate in your labels :). I think even 150ms will be too fast, so please consider a bigger value.

Also your implementation has functional issue because you drop UI dispatch inside UpdateUI , so you may report incorrect data to user, for example:

 1. t=0;  x=0;                   lastX=0 
 2. t=15; x=100; label=Right;    lastX=100
 3. t=20; x=300; <don't update>; lastX=300
 4. t=30; x=250; label=Left;     lastX=250

You reported to user that Pose moved to the Right when x=100 and then reported that it moved Left when x=250 what is not correct.

I'd recommend to move throttling logic to the loop so that you don't have this issue:

val sw = StopWatch.StartNew()
using (var frames = pipelines[1].WaitForFrames())
{
  if(sw.ElapsedMilliseconds > 150) continue;
  ...
  sw.Restart()
}

Other considerations

Pull vs Push

You are using push model by dispatching messages in your synchronization context. Instead you can pass these details through the state and update UI using Timer :

// Form fields:
Vector3 _currentLocation;
Vector3 _dispatchedLocation;
System.Windows.Forms.Timer _poseTimer;

private void Form1_Load(object sender, EventArgs e) {
 _poseTimer = new System.Windows.Forms.Timer(this.Container){Interval=150};
 _poseTimer.Tick += displayPoseChange;
}

private void displayPoseChange(object sender, EventArgs e)
{
 // compare _currentLocation to _dispatchedLocation
 // update labels
 // replace _dispatchedLocation with _currentLocation 
}

// ButtonClick method
poseTimer.Start()
...
using (var frames = pipelines[1].WaitForFrames()) {
  _currentLocation =  pose_data.translation // just update the field
  ...
}
...
poseTimer.Stop()
...

Rx.NET

You are processing frames from the pipeline and there is a great framework that could be very handy for that: Reactive Extensions

var pipeline = await initializePipeline();
var obs = Observable.FromEvent(
            callback => pipeline.Start(callback), 
            _ => pipeline.Stop());
obs.Where(Stream.Pose) // get only Poses
   .Select(f => f.As<PoseFrame>().PoseData) // select only what we need
   .Where(pose_data => pose_data.translation.x != -300) // filter out?
   .Sample(TimeSpan.FromMilliseconds(100)) // throttle the UI
   .Buffer(2, 1) // gives you last and current items
   .ObserveOn(this) // dispatch on the UI
   .Subscribe(data => {var last=data[0]; var current=data[1]; /* update labels*/});
await obs.GetAwaiter();

Code above shows you the idea but I didn't try to compile it, so you might need to alight types.

More documentation and examples .

You can use Background Worker in order to do the process without freezing the form. If the concern is on the speed of the process execution and it involves a loop, you can use Task-Parallel Library

Example is Parallel.Foreach

Parallel.ForEach(dev, device=>  {  
    config.EnableDevice(dev[i].Info[CameraInfo.SerialNumber]);
    config.EnableAllStreams();
    Pipeline p = new Pipeline(ctx);
    p.Start(config);
    pipelines[i] = p;
    i++;;  
}); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM