Processing tasks sequentially

我与影子孤独终老i 提交于 2019-12-13 23:29:32

问题


i have a base class JobBase that has a single method Execute() which returns a Task. There are several classes that inherits from JobBase and that do some asynchronous operations. I need to put instances of the inherited classes into a list and execute them sequentially, so having job1, job2, job3, etc. job2 shall start when job 1 is finished, job 3 shall start when job2 is finished, etc.

So the queue may contain an arbitrary number of jobs. For example: job1 downloads some content, job2 reads the content. In an another scenario, job1 downloads some content, job2 downloads some other content, job3 reads the content, etc.

Example

// The base class
public abstract class JobBase
{
    public abstract Task Execute();
}

// inherited jobs
public class Job1 : JobBase
{
    public override Task Execute()
    {
        // this one: return Task.Run()
        // or this one?: 
        Task task = new Task(async () =>
        {
            await Task.Delay(2000);
        });
        return task; 
    }
}

public class Job2 : JobBase
{
    public override Task Execute()
    {
        Task task = new Task(async () =>
        {
            await Task.Delay(1000);
        });
        return task; 
    }
}

The main program:

private static void Main(string[] args)
{
    var jobs = new List<JobBase>();
    jobs.Add(new Job1());
    jobs.Add(new Job2());

    List<Task> tasks = new List<Task>();
    for (int i = 0; i < jobs.Count; i++)
    {
        var jobNr = i;
        Task task = jobs[jobNr].Execute();
        tasks.Add(task);

        task.Start();
    }

    Console.WriteLine("Starting WaitAll");
    Task.WaitAll(tasks.ToArray());

    Console.WriteLine("Finished");
    Console.ReadKey();
}

Actually, the tasks are executed and i have no control aboute the order they are executed (or finished). Additionally, I would like to get a result (bool) of each job and continue with the next job, if the previous one was successful.

I read and tried the SequentialTaskScheduler, solutions with TaskCompletionSource, the tutorial about Processing tasks as they complete. I cannot work with ContinueWith since the number of jobs(tasks) is not known.

Please have a look at the following example and its output. For simplicity reasons there is one class Job that inherits from JobBase which takes an index and a delay value:

public class Job : JobBase
{
    private readonly int _index;
    private readonly int _delay;

    public Job(int index, int delay)
    {
        _index = index;
        _delay = delay;
    }

    public override Task Execute()
    {
        Task task = new Task(async () =>
        {
            Console.WriteLine("Job {0} before delay", _index);
            await Task.Delay(_delay);
            Console.WriteLine("Job {0} after delay", _index);
        });
        return task;
    }
}

Adding some instances to the task list, such as

jobs.Add(new Job(1, 3000));
jobs.Add(new Job(2, 2000));
jobs.Add(new Job(3, 1000));

I get the following result:

Starting WaitAll
Job 2 before delay
Job 3 before delay
Job 1 before delay
Finished
Job 3 after delay
Job 2 after delay
Job 1 after delay

(Job3 finishes at first, since the delay is less than job2, etc.)

The desired result is:

Starting WaitAll
Job 1 before delay
Job 1 after delay
Job 2 before delay
Job 2 after delay
Job 3 before delay
Job 3 after delay
Finished

What is the best approach to run the async tasks sequentially? I am free to change the signature of the Execute method, e.g returning Task or whatever.


回答1:


Try this:

async void Main(string[] args)
{
    var jobs = new List<JobBase>();
    jobs.Add(new Job(1, 3000));
    jobs.Add(new Job(2, 2000));
    jobs.Add(new Job(3, 1000));

    for (int i = 0; i < jobs.Count; i++)
    {
        var t = jobs[i].Execute();
        await t;
    }

    Console.WriteLine("Finished");
    Console.ReadLine();
}

Note that we are no longer using

 Task.WaitAll(tasks.ToArray());

since that allows the Tasks to run in parallel. We just await them in the loop.

Modify the Job class to this:

public class Job : JobBase
{
    private readonly int _index;
    private readonly int _delay;

    public Job(int index, int delay)
    {
        _index = index;
        _delay = delay;
    }

    public override Task Execute()
    {
        Task task = Task.Run(async () =>
        {
            Console.WriteLine("Job {0} before delay", _index);
            await Task.Delay(_delay);
            Console.WriteLine("Job {0} after delay", _index);
        });
        return task;
    }
}

Note the change of

Task task = new Task(async () =>

to

Task task = Task.Run(async () =>



回答2:


So the queue...

What queue? Your current code uses a List<T> with Task.WaitAll, which is fine if you know how many tasks you're waiting for. It doesn't work as a "task runner", because it's not possible to interrupt the WaitAll and say "oh, you have a new task now".

It sounds to me like what you're looking for is ActionBlock<T>:

private static void Main(string[] args)
{
  var jobs = new ActionBlock<JobBase>(x => x.Execute());
  jobs.Add(new Job1());
  jobs.Add(new Job2());
  jobs.Complete();

  Console.WriteLine("Starting Wait");
  Task.WaitAll(jobs.Completion.Wait()); // only using Wait because this code is in Main

  Console.WriteLine("Finished");
  Console.ReadKey();
}

ActionBlock<T> by default will execute one task at a time.

Additionally, I would like to get a result (bool) of each job and continue with the next job, if the previous one was successful.

If you report errors with exceptions, then the ActionBlock will stop processing on the first failed job.



来源:https://stackoverflow.com/questions/39603997/processing-tasks-sequentially

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!