Проблема многопоточности

Я перебираю строки в файле в каждой считаю кол-во букв "а", делаю это асинхронно и многопоточно. Почему в многопоточном методе кол-во букв "а" меньше, чем в асинхронном или в просто последовательном. То есть если перебрать строки и подсчитать буквы а, то последовательно будет 65913, асинхронно 65913, а многопоточно будет меньше, например 65909. Может быть какой-то поток мешает завершиться другому потоку? В чём проблема и как её решить? АСИНХРОННО

public static async Task<int> ReadFileAsync(string path)
{
    int count = 0;
    using (StreamReader sr =  File.OpenText(path))
    {
        string line = String.Empty;
        int i = 0;
        while ((line = await sr.ReadLineAsync()) != null)
        {
            if (!String.IsNullOrEmpty(line))
            {
                i++;
                MatchCollection matches3 = Regex.Matches(line, "а");
                count += matches3.Count;
                Console.WriteLine($"in line num {i} count symbol a equal: {matches3.Count}");
            }
        }
    }
    return count;
}

МНОГОПОТОЧНО

public static void ReadFileThread(string path)
{
    StreamReader sr = File.OpenText(path);
    string line = String.Empty;

    while ((line = sr.ReadLine()) != null)
    {
        if (!String.IsNullOrEmpty(line))
        {
            Thread thread = new Thread(new ParameterizedThreadStart(WorkWithLines));
            thread.Start(line);
        }

    }
}
private static void WorkWithLines(object line)
{
    MatchCollection matches3 = Regex.Matches(line.ToString(), "а");
    Console.WriteLine($"in line num count symbol a equal: {matches3.Count}");

    CountForThirdMethod += matches3.Count;
}

Ответы (2 шт):

Автор решения: PashaPash
CountForThirdMethod += matches3.Count;

+= -не атомарная операция. Она разбивается на две

var newCountForThirdMethod = CountForThirdMethod + matches3.Count;
CountForThirdMethod = newCountForThirdMethod;

При выполнении в несколько потоков может быть наложение вида

// CountForThirdMethod == 0
Thread1: var newCountForThirdMethod = CountForThirdMethod + matches3.Count; // 1
Thread2: var newCountForThirdMethod = CountForThirdMethod + matches3.Count; // 1
Thread1: CountForThirdMethod = newCountForThirdMethod; // 1
Thread2: CountForThirdMethod = newCountForThirdMethod; // 1

т.е. вроде оба потока сделали +=, но один из результатов потерялся. Огораживайте эту строчку lock-ом, или используйте Interlocked.Add

Но вообще вам вроде многопоточнось не нужна, скорее всего узким местом все равно будет чтение из файла.

→ Ссылка
Автор решения: CrazyElf
Thread thread = new Thread(new ParameterizedThreadStart(WorkWithLines));
thread.Start(line);

Это, конечно, прекрасно, но ваша программа может завершиться раньше, чем завершатся все запущенные таким образом потоки. Правильный шаблон работы с потоками - вы должны собрать собранные потоки в список, а потом сделать для всех запущенных потоков присоединение к основному потоку после их окончания:

thread.Join()

Скорее всего в вашем случае почти все потоки успевают завершиться успешно, кроме нескольких самых последних, судя по статистике. Вернее, вы успеваете где-то напечатать значение переменной CountForThirdMethod раньше, чем завершился последний поток (или несколько последних потоков).

Ну то есть примерно так:

var threads = new List<Thread>();
while ...
{
    ...
    Thread thread = new Thread(new ParameterizedThreadStart(WorkWithLines));
    thread.Start(line);
    threads.Append(thread);
    ...
}

foreach thread in threads
{
    thread.Join();
}
→ Ссылка