控制任务执行速度和暂停/恢复

时间:2016-04-29 04:11:09

标签: c# .net asynchronous async-await task-parallel-library

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

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();

现在我要做的是为此操作添加控件:暂停/恢复,步骤,停止。我还希望能够控制执行速度,例如,通过计算前一个和下一个数据段之间的时间戳差异来减慢处理速度以模拟实时数据输入,并增加处理下一个段所需的时间(基本上导致延迟,每个段之间的间隔变化)。 当我想到它时,我想实现一些可以控制我的任务的东西:

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

在研究我的选项时,我发现建议使用AsyncManualResetEventPauseToken来控制暂停/恢复,但我不确定如何将延迟与此相结合。

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

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

2 个答案:

答案 0 :(得分:1)

您的一个规格是逐步完成流程的能力。所以我假设你有一系列有序的步骤。通过这个我的意思是你有类似的东西:“执行第一步”和“执行下一步”,或者用软件术语:你的步骤序列是可枚举的。

如果你没有这样的序列,那你就不能步了。

如果您将流程视为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,这是一个相当小的改变。

  • 接口需要异步Task Step()函数
  • Step class需要异步函数返回Task并等待Step()
  • 而不是WaitAll使用await Task.WhenAll

使用示例:

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);
     }
 }

为了保持简单并专注于任务执行,我创建了Step类,以便每个步骤都不知道下一步。因此,步骤类不是Enumerable,Step类的用户应该跟踪已经执行的步骤。

改进是让步骤类实现IEnumerable。每一步都知道下一步。这意味着用户不必跟踪已执行哪些步骤以及哪些步骤已执行。如何制作一个调查员在其他地方讨论。

答案 1 :(得分:1)

如果我理解正确,你想要的是异步等待以下所有:

  • 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);
}