0

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.

KUL
  • 142
  • 9
  • 2
    What in the world are you doing? You're blocking the thread pool threads for everyone to create another thread for each to do some nonsense, then join on it? Creating dictionaries with `object` values because.. what, structs are too structured? – Blindy Jul 31 '20 at 14:42
  • What is the reason for `Parallel.ForEach`? You are not doing any CPU intensive work inside the loop. You are just starting a thread and then wait it to finish. Also, if the in-code comments are essential to understand the problem, could you translate them in English? – Theodor Zoulias Jul 31 '20 at 15:12
  • COM requires the STAThread model (which Thread provides), and I use Parallel.ForEach because it is convenient for Queuing. No, comments are not particularly useful. The question is more why (1 thread Parallel.ForEach) - > (1 thread Thread ?!) - > (1 COM interface ???) in the process of working, after 10 minutes in the background a few dozen EXCEL.EXE waiting processes from the COM interface ... – KUL Jul 31 '20 at 22:11
  • 1
    Excel doesn't require `STAThread` environment but single Thread for creation and interaction. And try it [asynchronously](https://stackoverflow.com/a/55582378/12888024). Don't wrap the code with the `Thread`, it's useless at all. And you can run few Excels e.g. max 4 instanses and open/close workbooks queue using it. Consider Producer/Consumer programming pattern as an approach of implementation. – aepot Aug 01 '20 at 01:42
  • Thanks for the advice, but this link has a slightly different idea - 1 document was opened and Sheets were processed asynchronously. In my case, need exactly 1 file, 1 handler, and make it a parallel queue. An additional internal thread is used because unpredictable situations occur when processing thousands of files and the thread can run indefinitely. I decided that if it is longer than 3 minutes, it means that something went wrong and you need to close the emergency, even if there is work. – KUL Aug 02 '20 at 06:51
  • Dear @aepot, you are absolutely right about `[STAThread]`. And it seems to have saved me from additional exceptions in my "flexible" architecture. – KUL Aug 03 '20 at 02:01
  • Never call `Thread.Abort` (unless you are trying to forcibly shut your app down). – Enigmativity Aug 04 '20 at 23:43
  • @Enigmativity `Thread.Abort` is used as a forced measure, because most likely the `COM` application is suspended in an undefined state. Although I wrote that I have solved the problem, but most likely it is not completely solved ... – KUL Aug 06 '20 at 00:15
  • In this scheme, COM Word works fine, but COM Excel does not feel well. Apparently Excel has a tricky model of parent-child COM interfaces and processes can't be killed just like that. Me may need more threads :). `Parallel.ForEach` (for the queue) -> `Thread` (for starting a standalone COM) -> inside the COM function, create `app = new Excel. Application ()` and `Tasks` (which waits less than 3 minutes, if there is nothing, then forcibly and CORRECTLY closes the COM app! and does not kill the process) whose task is to process the document `book = books.Open` – KUL Aug 06 '20 at 00:17
  • @KUL - You need to launch a new process to automate Excel if you think the app will get in an undefined state. Using threads is an exceedingly bad idea if you think you can't manage the lifetime of the threads without using `Abort`. – Enigmativity Aug 06 '20 at 00:30
  • @Enigmativity , you didn't understand a little, Excel is already in a separate thread ;) , i need another sub-thread, asynchronous, with waiting, to open a document that may not open well and give an unhandled warning. And then it is in the upper stream to complete the COM interface of Excel itself, and since I have the logic of COM 1 process for 1 document, everything should be fine (`Parallel` (queue) -> `Thread` (Excel App) ->(`Tasks` Excel book)). In general, i need to try ... – KUL Aug 06 '20 at 05:27
  • @KUL - I don't know what "it is in the upper stream to complete the COM interface of Excel itself" means. I do think you've misunderstood me. If you ever need to call `Abort` on a thread it is only when forcibly shutting down your app. In your case this means you main program needs to start a process to run C# code that connects to Excel - then you can cleanly close your process without affecting your main program. Effectively you would have three processes running. – Enigmativity Aug 06 '20 at 05:44
  • @Enigmativity Yes, unfortunately, I don't understand your idea ... maybe if you on the characters - > | < - tried to show ... – KUL Aug 06 '20 at 07:02
  • @KUL - (1) Launch your C# app - it creates a process that runs another C# app (2) that then does all of the communication with Excel. (1) knows nothing about Excel - it just communicates with (2). And (2) can be killed at any time without causing any grief to (1). – Enigmativity Aug 06 '20 at 07:08
  • @Enigmativity You described everything correctly! The problem is that when process (2) starts, it works with EXCEL via COM. There are files for which Excel may show a pop-up window or simply not open due to a failure of communication with AD RMS (if the file is encrypted), and formally the process (2) continues to work correctly, even though part of the COM calls did not give a result and the entire function in the stream (2) stopped on an infinite wait (where Excel waits for an interactive click Ok/Cancel (and the `app.Visible interface = false`)) ... But how then to correctly complete (2)? – KUL Aug 06 '20 at 07:38
  • @KUL - I don't understand what "But how then to correctly complete (2)" means? – Enigmativity Aug 06 '20 at 07:43

1 Answers1

0

I solved the problem. The problem was due to inattention or lack of understanding of COM types ...

For history, maybe someone will need: Overlaid checks and saw that EXCEL generates exceptions 0x800A03EC and later get:

Не удалось получить фабрику класса COM для компонента с CLSID {00024500-0000-0000-C000-000000000046} из-за следующей ошибки: 80080005 Ошибка при выполнении приложения-сервера (Исключение из HRESULT: 0x80080005 (CO_E_SERVER_EXEC_FAILURE))

The translation will probably be as follows:

Retrieving the COM class factory for component with CLSID {00024500-0000-0000-C000-000000000046} failed due to the following error: 80080005 Server execution failed (Exception from HRESULT: 0x80080005 (CO_E_SERVER_EXEC_FAILURE))

How it all happened:

  1. The external processor monitors the queue, creates a nested thread
  2. The Nested thread starts a function that creates a COM interface
  3. Under certain circumstances, it incorrectly resets the value and makes graphic.Filename = "" instead of the correct graphic.Filename = null
  4. COM handler "" takes this as an existing path to the file system, finds nothing, generates COM exceptions
  5. Since COM processing in try/catch, the exception is caught and the internal thread function is" correctly " completed, processing if (!t. Join(180000)) and killing the process is not required
  6. Since the CLR saw exceptions in COM, I jumped to catch, dead COM interfaces and processes in the task Manager accumulate EXCEL.EXE

p. s. COM is a terrible evil ...

KUL
  • 142
  • 9