简体   繁体   English

为什么使用Task.ContinueWith会伤害我的程序的响应能力?

[英]Why using Task.ContinueWith hurts my program's responsiveness?

We have a video player written in WPF with a scroll bar. 我们有一个用WPF编写的带滚动条的视频播放器。 When the scroll bar is dragged right-left the CurrentFrameTime is updated and triggers UpdateFrames which, in turn, grabs the frame and shows it. 当滚动条左右拖动时, CurrentFrameTime会更新并触发UpdateFrames ,而UpdateFrames会抓取框架并显示它。 That works fine. 这很好。

But, sometimes grabbing the frame can take time (because of the disk for example) and though CurrentFrameTime value can be already changed, the UpdateFrames can be "stuck" and still be waiting for previous frame in ...GetAsync().Result . 但是,有时抓取帧可能需要一些时间(例如因为磁盘),虽然CurrentFrameTime值已经可以更改,但UpdateFrames可能会“卡住”并仍在等待...GetAsync().Result前一帧...GetAsync().Result What is decided to do is to move Dispatcher.BeginInvoke into ContinueWith block. 决定将Dispatcher.BeginInvoke移动到ContinueWith块中。 Now, each time the CurrentFrameTime is changed, the previous operation will be canceled(we don't need to show the frame if frame time was already changed) and an up-to-date frame should be shown. 现在,每次更改CurrentFrameTime ,前一个操作都将被取消(如果帧时间已经更改,我们不需要显示帧),并且应该显示最新的帧。 But, for some reason because of this change the application became slower. 但是,由于某种原因,由于这种变化,应用程序变得更慢。 When I drag the scroll it can take a few seconds before the image is updated. 当我拖动滚动时,可能需要几秒钟才能更新图像。

What could happened that moving the code into ContinueWith has slowed down the video player? 将代码移入ContinueWith可能会减慢视频播放器的速度?

MainApplication without ContinueWith 没有ContinueWith的MainApplication

_threadUpdateUI = new Thread(new ThreadStart(UpdateFrames));

public long CurrentFrameTime
{
  get{...}
  set
  {
     ...
     _fetchFrame.Set();
  }
}

void UpdateFrames()
{
    while(run)
    {
       _fetchFrame.WaitOne();

       var frame = Cache.Default.GetAsync(CurrentFrameTime)
                                .Result;
       Dispatcher.BeginInvoke(new Action(() => ShowFrame(frame.Time, frame.Image)));           
    }
}

Cache 高速缓存

public Task<VideoFrame> GetAsync(long frameTime)
{
    //this i used when cache is disabled
    if (GrabSynchronously)
    {
        var tcs = new TaskCompletionSource<VideoFrame>();  
        //reading from file      
        var frame2 = FrameProvider.Instance.GetFrame(frameTime);                
        tcs.SetResult(frame2);
        return tcs.Task;
    }

    ...
}

MainApplication WITH ContinueWith MainApplication WITH ContinueWith

void ShowFrames()
{
   while(run)
   {
      _fetchFrame.WaitOne();

      _previousFrameCancellationToken.Cancel();
      _previousFrameCancellationToken = new CancellationTokenSource();

      Cache.Default.GetAsync(CurrentFrameTime).ContinueWith((task) =>
      {
          var frameTime = task.Result.Time;
          var frameImage = task.Result.Image
          Dispatcher.BeginInvoke(new Action(() => ShowFrame(frameTime, frameImage)));
      }, _previousFrameCancellationToken.Token);    
   }
}

df DF

In your old way your UpdateFrames loop would block every .Result call. 以旧的方式, UpdateFrames循环将阻止每个.Result调用。 This made your loop self metering, only allowing one request "in flight" at a time even if _fetchFrame got .Set() called many times while it was waiting for .Result to finish. 这使你的循环自计量,只允许“飞行中”一个请求中同时即使_fetchFrame.Set()调用多次,而它正在等待.Result完成。

In your new way every call to _fetchFrame.Set() triggers another task to start up and be "in flight" (assuming GrabSynchronously is false) even if it never gets used. 以新的方式,每次调用_fetchFrame.Set()触发另一个启动任务并且“正在飞行”(假设GrabSynchronously为false),即使它从未被使用过。 This is flooding your system with requests and is causing your slowdown. 这会使您的系统充满请求并导致您的速度减慢。

One possible solution is to put another semaphore of some type to limit the number of concurrent requests for frames you can handle. 一种可能的解决方案是放置某种类型的另一个信号量来限制可以处理的帧的并发请求数。

Semaphore _frameIsProcessing = new Semaphore(5, 5); //Allows for up to 5 frames to be requested at once before it starts blocking requests.

private void ShowFrames()
{
    while (run)
    {
        _fetchFrame.WaitOne();

        _previousFrameCancellationToken.Cancel();
        _previousFrameCancellationToken = new CancellationTokenSource();

        _frameIsProcessing.WaitOne();
        Cache.Default.GetAsync(CurrentFrameTime).ContinueWith((task) =>
        {
            _frameIsProcessing.Release();

            if(_previousFrameCancellationToken.IsCancellationRequested)
                return;

            var frameTime = task.Result.Time;
            var frameImage = task.Result.Image;
            Dispatcher.BeginInvoke(new Action(() => ShowFrame(frameTime, frameImage)));
        });
    }
}

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

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