Мало контекста, у вас консольное приложение, WinForms или WPF? Если WinForms или WPF - зависание вызвано тем, что ваш код выполняется в UI потоке. Для начала можно попробовать добавить
.ConfigureAwait(false)
к вашим асинхронным вызовам, только потом нужно не забыть все операции с UI после асинхронных вызовов перенаправить в UI поток (
Dispatcher.Invoke
).
Далее, как уже верно подмечено в комментариях - блок try/catch должен быть внутри цикла (из вашего примера непонятно как он расположен).
Отвечая на ваш изначальный вопрос - скорее всего вам совершенно необязательно выделять отдельный поток, вы можете написать что-то вроде:
_downloadTask = Task.Run(MySuperDownloaderMethod);
Где
MySuperDownloaderMethod
- асинхронный метод, выполняющий загрузку файлов. Далее там, где вам нужно подождать - пишете
await _downloadTask;
Если не устраивает такой вариант или вам очень нужен выделенный отдельный поток, вы можете завести у себя в классе поле типа
TaskCompletionSource<object> _downloadTcs = new();
, и после скачивания всех изображений выполнить
_downloadTcs.SetResult()
, а там, где хотите подождать - вызвать
await _downloadTcs.Task;
Если хочется чего-то взрослого, тогда используйте AutoResetEvent. Определяете в классе поле типа
AutoResetEvent _downloadEvent = new(false);
, после загрузки всех файлов вызываете
_downloadEvent.Set()
, там где нужно подождать -
_downloadEvent.WaitOne();
С потоком что неудобно - вам придётся его явно ждать при помощи
Thread.Join();
, что не всегда удобно. К тому же, если поток не создан как Background и не завершён на момент завершения программы - программа будет ждать его завершения, что тоже не всегда удобно и очевидно. Поэтому, если нет жёсткой необходимости в потоке, лучше используйте Task.
Вообще рекомендую почитать
www.albahari.com/threading - очень хорошее чтиво на тему многопоточности.