Код не читал, но попробуй так:
У меня есть задача: есть много текстовых файлов, заполненных рандомным текстом, в котором иногда встречаются имэйлы.
после того, как какой-то поток считал имэйлы, он должен записать их на один файл, при этом всех три потока должны записывать считаннные данные на этот один файл, не на разные файлы.
1. Для каждого из файлов запусти по потоку (таске), каждый из которых пусть свой файл читает в поисках имеилов.
2. Для записи в итоговый файл - заведи ещё 1 поток (таску).
3. Коммуникацию между N читающими и 1 пишущим организуй через System.Threading.Channel
Таким образом ты избавишься от ненужных блокировок и затрат на синхронизацию доступа к какому-то списку.
UPD: в двух и больше файлах могут быть несколько одинаковых имэйлов и считывать мне надо именно первую копию имэйла из первого файла, где эта копия встретилась, если этот имэйл имеет несколько копий в разных файлах.
UPD: тогда смотрим на количество данных.
Если имеилов мало (по сравнению с количеством ОЗУ), то тогда можем прямо в памяти держать HashSet и проверять его в пишущем потоке.
Если имеилов побольше - можем сделать HashSet не по самим и имеилам, а по их хешам.
Если имеилов совсем много, то тогда можно записывать в отсортированную структуру данных на диск (двоичное дерево поиска например).
UPD2:
Для каждого из файлов запусти по потоку (таске), каждый из которых пусть свой файл читает в поисках имеилов.
На самом деле можно попробовать запустить несколько потоков, разделив каждый файл ещё на N сегментов и назначив потокам эти сегменты. Плодить новые можно до тех пор, пока у тебя IO не кончится.