[英]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.