Dev
Dev

Reputation: 1223

How do I run a method both parallel and sequentially in C#?

I have a C# console app. In this app, I have a method that I will call DoWorkAsync. For the context of this question, this method looks like this:

private async Task<string> DoWorkAsync()
{
  System.Threading.Thread.Sleep(5000);

  var random = new Random();

  var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  var length = random.Next(10, 101);

  await Task.CompletedTask;
  return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

I call DoWorkAsync from another method that determines a) how many times this will get ran and b) if each call will be ran in parallel or sequentially. That method looks like this:

private async Task<Task<string>[]> DoWork(int iterations, bool runInParallel)
{
  var tasks = new List<Task<string>>();
  for (var i=0; i<iterations; i++)
  {
    if (runInParallel)
    {
      var task = Task.Run(() => DoWorkAsync());
      tasks.Add(task);
    }
    else
    {
      await DoWorkAsync();
    }
  }

  return tasks.ToArray();
}

After all of the tasks are completed, I want to display the results. To do this, I have code that looks like this:

var random = new Random();
var tasks = await DoWork(random.Next(10, 101);
Task.WaitAll(tasks);

foreach (var task in tasks)
{
  Console.WriteLine(task.Result);
}

This code works as expected if the code runs in parallel (i.e. runInParallel is true). However, when runInParallel is false (i.e. I want to run the Tasks sequentially) the Task array doesn't get populated. So, the caller doesn't have any results to work with. I don't know how to fix it though. I'm not sure how to add the method call as a Task that will run sequentially. I understand that the idea behind Tasks is to run in parallel. However, I have this need to toggle between parallel and sequential.

Thank you!

Upvotes: 0

Views: 1250

Answers (3)

Guru Stron
Guru Stron

Reputation: 143453

the Task array doesn't get populated.

So populate it:

else
{
  var task = DoWorkAsync();
  tasks.Add(task);
  await task;
}

P.S.

Also your DoWorkAsync looks kinda wrong to me, why Thread.Sleep and not await Task.Delay (it is more correct way to simulate asynchronous execution, also you won't need await Task.CompletedTask this way). And if you expect DoWorkAsync to be CPU bound just make it like:

private Task<string> DoWorkAsync()
{
    return Task.Run(() =>
    {
        // your cpu bound work
        return "string";
    });
}

After that you can do something like this (for both async/cpu bound work):

private async Task<string[]> DoWork(int iterations, bool runInParallel)
{
    if(runInParallel)
    {
        var tasks = Enumerable.Range(0, iterations)
            .Select(i => DoWorkAsync());
        return await Task.WhenAll(tasks);
    }
    else
    {
        var result = new string[iterations];
        for (var i = 0; i < iterations; i++)
        {
            result[i] = await DoWorkAsync();
        }
        return result;
    }
}

Upvotes: 5

Pac0
Pac0

Reputation: 23174

I think you'd be better off doing the following:

  1. Create a function that creates a (cold) list of tasks (or an array Task<string>[] for instance). No need to run them. Let's call this GetTasks()

var jobs = GetTasks();

  1. Then, if you want to run them "sequentially", just do
var results = new List<string>();
foreach (var job in jobs)
{
    var result = await job;
    results.Add(result);
}
return results;

If you want to run them in parallel :

foreach (var job in jobs) 
{
    job.Start();
}
await results = Task.WhenAll(jobs);

Another note,

All this in itself should be a Task<string[]>, the Task<Task<... smells like a problem.

Upvotes: 1

Johnathan Barclay
Johnathan Barclay

Reputation: 20373

Why is DoWorkAsync an async method?

It isn't currently doing anything asynchronous.

It seems that you are trying to utilise multiple threads to improve the performance of expensive CPU-bound work, so you would be better to make use of Parallel.For, which is designed for this purpose:

private string DoWork()
{
    System.Threading.Thread.Sleep(5000);

    var random = new Random();

    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    var length = random.Next(10, 101);
    
    return new string(Enumerable.Repeat(chars, length)
        .Select(s => s[random.Next(s.Length)]).ToArray());
}

private string[] DoWork(int iterations, bool runInParallel)
{
    var results = new string[iterations];

    if (runInParallel)
    {
        Parallel.For(0, iterations - 1, i => results[i] = DoWork());
    }
    else
    {
        for (int i = 0; i < iterations; i++) results[i] = DoWork();
    }
    
    return results;
}

Then:

var random = new Random();
var serial = DoWork(random.Next(10, 101));
var parallel = DoWork(random.Next(10, 101), true);

Upvotes: 1

Related Questions