Здесь вам определенно нужен какой-то механизм блокировки. Лично я бы использовал ConcurrentDictionary для этого. Или, в зависимости от того, что вы пытаетесь выполнить sh здесь, вы можете просто сделать это:
var list = new List<Task>();
list.Add(DoStuff(1));
list.Add(DoStuff(2));
list.Add(DoStuff(3));
await Task.WhenAll(list);
Это обеспечит выполнение всех задач, прежде чем продолжить.
*** Редактировать - обновить
Если вы хотите, чтобы они постоянно удалялись из списка - в зависимости от того, как вы просматриваете их, вы можете просто сделать это вместо:
private List<Task> _list = new List<Task>();
...
_list.Add(DoStuff(1));
_list.Add(DoStuff(2));
_list.Add(DoStuff(3));
Тогда, когда вам нужно увидеть, сколько осталось, вы можете просто сделать это:
list.Count(a => !a.IsCompleted);
По сути, IsCompleted имеет значение true, только если этот поток завершен до завершения. Однако вам также может понадобиться проверить IsFapted или IsCanceled, в зависимости от того, что делают эти задачи.
*** Редактировать обновление 2
В потоке, который добавляет в этот список, вы затем можете удалить их, не беспокоясь:
_list.Add(DoStuff(4));
_list.Add(DoStuff(5));
_list = _list.Where(a => !a.IsCompleted).ToList();
Если вы не хотите связываться с фактической ссылкой на переменную, вы можете сделать задний ход для l oop и удалить вручную, пока другой Тема не добавляется в этот список.