Блок completion нигде не выполняется и никуда не идет, он начинает выполняться, когда запрос выполнился так или иначе. А main thread никого ждать не должен. Просто в нужное время сессия вызовет completion-блок, действительно, не в главном потоке.
Немного советов и схема. Возможно предложу не идеальный вариант.
1. Выпиливаем все до создания сессии в отдельный метод, оно и логично - мы все подготавливаем к дальнейшей работе.
2. Пишем отдельно метод, который создает таск, при помощи которого будем логиниться(то что в вашем методе после создания сессии). Создаем сам таск при помощи метода
dataTaskWithRequest:completionHandler:, completion-блок вызовется когда таск выполнится тем или иным образом. Если успех - к п.3
3. Вызываем отдельный метод внутри этого блока, где будем писать дальнейшую логику после логина, туда можно накидать создание других тасков. Метод лучше вызвать в главном потоке, т.к. наверняка захочется что-то сделать с UI.
[self performSelectorOnMainThread:@selector(afterLoginLogicMethod) withObject:nil waitUntilDone:NO];
P.S. По поводу callback'a от тасков
Можно его получать через completion-блоки, а можно через делегатные методы, кому как удобнее.