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

时间:2015-02-27 21:39:26

标签: c# wpf multithreading task-parallel-library

我们有一个用WPF编写的带滚动条的视频播放器。当左右拖动滚动条时,CurrentFrameTime会更新并触发UpdateFrames,然后抓取框架并显示它。这很好。

但是,有时抓取框架可能需要一些时间(例如因为磁盘),虽然CurrentFrameTime值已经改变,但UpdateFrames可能会被卡住"并且仍在等待...GetAsync().Result中的上一帧。 决定将Dispatcher.BeginInvoke移至ContinueWith区块。现在,每次更改CurrentFrameTime时,前一个操作都将被取消(如果帧时间已经更改,我们不需要显示帧),并且应显示最新的帧。但是,由于某种原因,由于这种变化,应用程序变得更慢。当我拖动滚动时,可能需要几秒钟才能更新图像。

将代码移入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)));           
    }
}

缓存

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

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

1 个答案:

答案 0 :(得分:3)

以旧的方式,UpdateFrames循环会阻止每次.Result来电。这使你的循环自我测量,只允许一个请求&#34;在飞行中&#34;即使_fetchFrame.Set()等待.Result完成时多次调用_fetchFrame.Set(),也会一次。

以新的方式,每次拨打GrabSynchronously都会触发另一项启动任务,并在飞行中#34; (假设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))); }); } } 为假)即使它永远不会被使用。这会使您的系统充满请求并导致您的速度减慢。

一种可能的解决方案是放置某种类型的另一个信号量来限制可以处理的帧的并发请求数。

{{1}}