1

I'm creating a list in C# synchronously but it's slow. There's about 50 items in the list and it can take up to 20 seconds to add each item. The method to add the item can be asynchronous so I'm wondering if I can add all of them using async/await somehow.

Here's a simple example of adding 2 items to a list synchronously. How would I do it asynchronously? Is it even possible? Would you get "collisions" if you tried to add to the list at the same time?

using System.Threading;
using System.Collections.Generic;

class Program
{

    static void Main(string[] args)
    {
        List<int> lst = null;
        lst=GetList();
    }
    

    static List<int> GetList()
    {
        List<int> lst = new List<int>();

        //add to list
        lst.Add(Task1()); 
        lst.Add(Task2()); 

        //return list
        return lst;
    }

    static int Task1()
    {
        Thread.Sleep(10000);
        return 1; 
    }

    static int Task2()
    {
        Thread.Sleep(10000);
        return 2; 
    }

}

Compufreak's answer was just what I was looking for. I have a related question although I'm not sure if this is how I'm supposed to ask it. I modified Compufreak's code slightly to a way that makes more sense to me and I'm wondering if there is any actual difference in the way the code works. I took the async/await out of Task1 and Task2 and just created a Task and passed Task1 and Task2 in as parameters.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;

class Program
{

    static async Task Main(string[] args)
    {
        List<int> lst = null;
        DateTime StartTime = DateTime.Now;
        lst = await GetList();
        DateTime EndTime = DateTime.Now;
        Console.WriteLine("ran tasks in " + (EndTime - StartTime).TotalSeconds + " seconds");
        Console.ReadLine();
    }


    static async Task<List<int>> GetList()
    {
        var tasks = new List<Task<int>>();

        //add task to list of tasks
        tasks.Add(new Task<int>(Task1));
        tasks.Add(new Task<int>(Task2));
        
        //start tasks
        foreach(Task<int> t in tasks)
        {
            t.Start();
        }

        //await results
        int[] results = await Task.WhenAll(tasks);

        //return list
        return results.ToList();
    }

    static int Task1()
    {
        Thread.Sleep(10000);
        return 1;
    }

    static int Task2()
    {
        Thread.Sleep(10000);
        return 2;
    }

}

7
  • You should look in to async/await. It was designed for scenarios like this (or other TPL type patterns like Parallel.ForEach) Commented Aug 4, 2020 at 17:21
  • @tnw That seems like a different scenario. In my case each "add" takes about 10 seconds to calculate the value to add. Commented Aug 4, 2020 at 17:26
  • @Andy Yes I tried to do it using async/await but I'm new to it and I'm not even sure it's possible in this case. Commented Aug 4, 2020 at 17:27
  • Take a look at the answer below. It's a really good example. Keep in mind the Async/Await pattern is a very deep rabbit hole, but, it is invaluable in todays coding standards. Better to learn it sooner than later. Commented Aug 4, 2020 at 17:28
  • 1
    Although the linked question changed after my last reopen-vote I still think this is no 100% match. The now linked answer is about awaiting tasks with different result types. This one is about awaiting tasks with equal result types. The difference is that you can directly use the array returned by Task.WhenAll in this case instead of handling the results seperately like in the linked answer. Commented Aug 4, 2020 at 18:07

2 Answers 2

3

No, you cannot add to a list asynchronously.

You need to change your pattern here - if you have many long running tasks and want to run them asynchronously you should initiate those tasks seperately and collect/transform the results afterwards:

static async Task Main(string[] args)
{
    List<int> lst = null;
    DateTime StartTime = DateTime.Now;
    lst = await GetList();
    DateTime EndTime = DateTime.Now;
    Console.WriteLine("ran tasks in " + (EndTime - StartTime).TotalSeconds + " seconds");
 }


  static async Task<List<int>> GetList()
  {
      var tasks = new List<Task<int>>();
      tasks.Add(Task1());
      tasks.Add(Task2());
      int[] results = await Task.WhenAll(tasks);
      //return list
      return results.ToList();
  }

 static async Task<int> Task1()
 {
     await Task.Delay(10000);
     return 1;
 }

 static async Task<int> Task2()
 {
     await Task.Delay(10000);
     return 2;
 }

This will output:

ran tasks in 10,0727812 seconds

Here is a slightly modified fiddle to try it out online. I reduced the delay and added an additional output there.

Alternatively you could use a shared concurrentbag to add the results inside the tasks (adding results to lists in seperate threads might cause weird issues like null entries), but I see no good reason for this in your usecase.

P.S.: Your functions need to be async at their core, otherwise you need to use something like Parallel.ForEach. Async should usually be prefered if possible as it uses your resources more effectively.

Sign up to request clarification or add additional context in comments.

3 Comments

That looks promising. Let me try that route.
I respect that someone downvoted this - but next time please leave an explanation in the comments so I can improve in the future.
Your answer was 5 stars!
0

The key to remaining threadsafe is to use an IProgress implementation like Progress and pass that instance to your task. That way all the threads are reporting back to a manager thread for the actual addition, but the work is being performed on seperate threads.

Before starting tasks

Progress prog = new Progress(p=>lst.Add(p));

Task function

int Tast1(IProgess prog)
{
   int status = 0;
   Sleep(1000);
   prog.Report(1)
   status = 1;
   return status;
}

2 Comments

Is this easier than async/await? Is async/await even possible under my scenario? I'm trying to determine the best method before I start trying to figure out how to do it.
If you're only processing 1 item per thread then what you outlined would be fine. If you want multiple threads to loop on a dataset then IProgress will update the main thread.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.