控制任务执行速度和pause/resume

Control Task execution speed and pause/resume

我的程序中有数据处理可能需要很长时间。从本质上讲,它是一个循环,以小段的形式从文件中读取数据,并将其逐步应用于数据模型。简化的代码如下所示:

public void LoadFromFile()
{
    while (this.playbackFile.HasMoreData)
    {
        this.ProcessData(this.playbackFile); // Process a single data segment
    }
}

为了保持我的 WPF UI 响应,我将它包装到一个任务中,如下所示:

public Task LoadFile()
{
    return Task.Run(() => this.LoadFromFile());
}

然后在单击按钮开始时简单地使用它:

await this.playbackController.LoadFile();

现在我要做的是为这个操作添加控件:pause/resume,step,stop。我还想要一种控制执行速度的能力,例如,通过计算前后数据段之间的时间戳差异并根据该差异增加处理下一段所需的时间来减慢模拟实时数据输入的处理速度(本质上导致具有在每个段之间变化的间隔的延迟)。 当我想到它时,我想实现一些可以像这样控制我的任务的东西:

WaitFor(x); // Delay for x ms to simulate speed decrease
WaitFor(-1); // Indefinite delay on pause; waits until resume is signaled

在研究我的选择时,我发现了使用 AsyncManualResetEvent or PauseToken 来控制 pause/resume 的建议,但我不确定如何将延迟与它结合起来。

对于我来说,让每个段处理一个单独的任务而不是一个大任务是有意义的(实际上,最好在数据段处理之间暂停,而不是在操作中间,并且步进需要无论如何),然后我可以用 Task.Delay 之类的东西来控制它们中的每一个,但我不确定这是否是个好主意,以及它是否允许我做 pause/resume.

如果有人能为此指出正确的方向,我将不胜感激。

您的规格之一是能够逐步完成您的流程。所以我假设你有一个有序的步骤序列。我的意思是你有类似的东西:"perform the first step" 和 "perform the next step",或者用软件术语来说:你的步骤顺序是可枚举的。

如果你没有这样的序列,那你就不能踩

如果您将流程视为一个 IEnumerable ,并且每个 Step 都有一个 Perform 函数,那么您可以使用 linq 来执行您想要的步骤序列。并非所有这些都有用

  • 从头开始执行所有步骤
  • 从头开始执行三个步骤
  • 执行下一步
  • 执行接下来的五个步骤
  • 继续步进直到步 == ...
  • 继续步进,直到执行完所有步骤
  • 跳过以下三步

另外一个规范是你想控制一个步骤使用的时间。换句话说:如果一个步骤的执行时间少于要求的时间,则等待直到超过要求的时间。

当然,你不能在比实际需要执行的步骤更短的时间内执行步骤。

有了这些规范,如果你的所有步骤实现以下接口就足够了:

public interface ISteppable
{
    void Step();
}

下面应该做的

public class Step
{
    private ISteppable SteppableObject {get; set;}
    public Step(ISteppable steppableObject)
    {
        this.SteppableObject = steppableObject;
    }


    // just do the step
    public void Step()
    {
        this.SteppableObject.Step();
    }

    // do the step, and control execution speed:
    public void Step(TimeSpan minExecutionTime)
    {
        var waitTask = Task.Delay(minExecutionTime);
        var stepTask = Task.Run( () => this.SteppableObject.Step());
        // wait until both the step and the wait task are ready:
        Task.WaitAll( new Task[] {waitTask, stepTask}
        // now yo know this lasted at least minExecutionTime
    }

如果您希望使用 async-await,这是一个相当小的改变。

  • 接口需要异步任务 Step() 函数
  • Step class 需要 return 任务并等待 Step()
  • 的异步函数
  • 使用 await Task.WhenAll
  • 代替 WaitAll

用法示例:

public class MyProcess()
{
    private ISteppable Step0 = new ...
    private ISteppable Step1 = new ...
    private ISteppable Step2 = new ...
    private ISteppable Step3 = new ...

    private IEnumerable<Step> steps = 
    {
        new Step(step0),
        new Step(step1),
        new Step(step0), // yes, my process needs step0 again!
        new Step(step2),
     }

     public IEnumerable<Step> Steps {get{return this.steps;}
 }

 static void Main()
 {
     MyProcess process = new MyProcess();
     // 3 steps full speed:
     foreach (var step in process.Steps.Take(3))
     {
         process.Step();
     }
     // slow speed until step == ...
     foreach (var step in process.Steps.Skip(3).While(step => step != ...)
     {
         process.Step(TimeSpan.FromSeconds(3);
     }
 }

为了让事情简单并专注于任务执行,我创建了步骤 class,这样每个步骤都不知道下一步。因此,步骤 class 不是可枚举的,步骤 class 的用户应该跟踪已经执行了哪些步骤。

一个改进是让步骤 class 实现 IEnumerable。每一步都知道下一步。这意味着用户不必跟踪哪些步骤已经执行,哪些没有执行。如何制作枚举器在别处讨论。

如果我没理解错的话,你想要的是异步等待以下所有内容:

  • AsyncManualResetEvent.WaitAsync()步进
  • PauseToken.WaitWhilePausedAsync() 暂停
  • Task.Delay() 减速

幸运的是,有一种方法可以做到这一点:Task.WhenAll()

AsyncManualResetEvent steppingEvent;
PauseToken pauseToken;
TimeSpan delay; // set to Zero for no delay

while (this.playbackFile.HasMoreData)
{
    var delayTask = Task.Delay(delay);

    this.ProcessData(this.playbackFile); // Process a single data segment

    await Task.WhenAll(
        steppingEvent.WaitAsync(), pauseToken.WaitWhilePausedAsync(), delayTask);
}