简体   繁体   English

如何使Winforms UI更新更快?

[英]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. 我正在捕获帧一会儿,然后获取数据并使用它更新UI,但是它突然出现了。 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: 如果设备初始化不需要同步上下文,则只需将整个try主体包括在任务中:

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. 初始化将在线程池线程中分派,UI不会冻结。 Please note that creating methods that are async and return void is a bad practice , event handler is an exception though. 请注意,创建async并返回void是一种不好的做法 ,但是事件处理程序是一个例外

UI dispatch UI调度

Inside of UpdateUI you dispatch 6 messages to synchronization context to update 6 labels. UpdateUI内部,您将6条消息调度到同步上下文以更新6个标签。 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). 将变量捕获到闭包上的开销很小,但是,在UI线程中调度的事件较少,并且也不会通过转换为Post方法(第二个参数)中的object来对所有事件进行装箱。

DateTime.Now DateTime.Now

DateTime.Now is rather expensive operation as it converts time to current timezone. DateTime.Now是相当昂贵的操作,因为它将时间转换为当前时区。 DateTime.UtcNow is more lightweight. DateTime.UtcNow更轻巧。 However, for your purposes I think Stopwatch will be much more suitable. 但是,出于您的目的,我认为秒表将更适合。

Throttling inside UpdateUI 在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: 如果帧之间的时间少于15毫秒,则不会更新UI:

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 :). 请注意,15ms是一个很小的值,我敢打赌,没有用户能够在您的标签中处理这样的刷新率:)。 I think even 150ms will be too fast, so please consider a bigger value. 我认为即使150ms也太快了,因此请考虑使用更大的值。

Also your implementation has functional issue because you drop UI dispatch inside UpdateUI , so you may report incorrect data to user, for example: 另外,您的实现也存在功能问题,因为您将UI分发放在UpdateUI ,因此您可能会向用户报告错误的数据,例如:

 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. 您向用户报告了x=100时Pose Right移动,然后报告x=250时Pose Left移动是不正确的。

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 : 相反,您可以通过状态传递这些详细信息,并使用Timer更新UI:

// 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 Rx.NET

You are processing frames from the pipeline and there is a great framework that could be very handy for that: Reactive Extensions 您正在处理管道中的框架,并且有一个很好的框架可能对此非常方便: 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. 您可以使用Background Worker来执行该过程而无需冻结表单。 If the concern is on the speed of the process execution and it involves a loop, you can use Task-Parallel Library 如果关注的是流程执行的速度并且涉及循环,则可以使用Task-Parallel库

Example is Parallel.Foreach 示例是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++;;  
}); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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