There is a certain queue, need to go through it with a certain step and create a thread (for each step). The thread, in turn, will run a function that works with COM (need a separate thread, because the Internet writes that COM will not hurt so much).
// Для Excel асинхронность очень вредна, т.к. можно поймать COM исключения 0x800A03EC, 0x800AC472 и подобные
// т.к. Excel использует главный поток и при закрытии COM дочерние потоки могут потерять COM интерфейсы
parallelOpt.MaxDegreeOfParallelism = 1;
// Для COM нужен отдельный, личный поток, поэтому многопоточность делаем так
Parallel.ForEach(listsFileInfoExcel, parallelOpt, (listsBlock) =>
{
    try
    {
        Thread t = new Thread(OfficeClass.ExcelProcessing);
        // Данным id будем связывать текущий поток и COM процесс
        string idThread = Guid.NewGuid().ToString();
        lock (objWorkThreadExcelLock)
        {
            dictThreadExcel.Add(idThread, 0);
        }
        try
        {
            t.IsBackground = false;
            t.Start(new Dictionary<string, object> {
                                                            {"listBlock",listsBlock},
                                                            {"pathHeaderImage",ImageHeaderExcel},
                                                            {"pathBackgroundImage",ImageBackgroundExcel},
                                                            {"Action",actionForDocument},
                                                            {"idThread",idThread}
                                                });
            // Скорее всего внешний COM процесс завис
            if (!t.Join(180000))
            {
                throw new Exception($"Принудительно завершён поток с файлами {String.Join(", ", listsBlock.Select(obj => obj.FullName).ToArray())}");
            }
        }
        catch (Exception ex)
        {
            AddLog(TypeLog.Error, ex.Message);
            t.Abort();
            lock (objWorkThreadExcelLock)
            {
                if (dictThreadExcel[idThread] != 0)
                {
                    Killing(dictThreadExcel[idThread]);
                }
            }
        }
        finally
        {
            lock (objWorkThreadExcelLock)
            {
                dictThreadExcel.Remove(idThread);
            }
        }
    }
    catch { }
});
Interesting features occur only with Excel processing. Maybe it's something to do with the COM architecture? https://stackoverflow.com/a/12893711/4143902 (by the way, because of catching this error, I lowered it to 1 thread)
If I run this code on a fast computer with a fast file system, then 1-2 processes appear in the task Manager EXCEL.exe, but if running on a weak VM with a classic disk system, the processes may have about 35 EXCEL.EXE. Question, what am I missing? How do I ask asynchronous loops not to do a new iteration without making sure that the thread inside has ended/died??? p. s. for Word, if specify 4 threads, it keeps 4-5 processes in the Manager.
