Выполнение COM-вызовов из одного потока приводит к зависанию потока - PullRequest
3 голосов
/ 13 апреля 2011

У меня есть приложение, которое выполняет некоторые функции автоматизации с помощью надстройки автоматизации. Эта надстройка является многопоточной, и все потоки могут совершать вызовы к COM-объектам Excel. Поскольку Excel может иногда возвращать исключение «занято» при выполнении нескольких вызовов, я завернул все свои вызовы в функцию «повтор». Однако я чувствую, что это неэффективно. Сейчас я пытаюсь сделать все вызовы к объектам Excel в одном потоке, чтобы все вызовы были «сериализованы» мной, что снижает риск того, что Excel вернет исключение «занято». Однако, когда этот поток пытается получить доступ к объекту Excel, приложение зависает. Я пытался установить нить в STA или MTA, но безрезультатно.

Код, который я использую для запуска всего из одного потока, выглядит следующим образом: «Оскорбительная» часть должна быть в «DoPass», возможно, способ, которым я вызываю Делегата, как-то не так.

public static class ExcelConnector
{
    public static Thread _thread;
    private static int ticket;
    public static Dictionary<Delegate, int> actionsToRun = new Dictionary<Delegate, int>();
    public static Dictionary<int, object> results = new Dictionary<int, object>();


    static ExcelConnector()
    {
        LaunchProcess();
    }

    public static int AddMethodToRun(Delegate method)
    {
        lock (actionsToRun)
        {
            ticket++;
            actionsToRun.Add(method, ticket);
        }
        return ticket;
    }

    public static bool GetTicketResult(int ticket, out object result)
    {
        result = null;
        if (!results.ContainsKey(ticket))
            return false;
        else
        {
            result = results[ticket];
            lock (results)
            {
                results.Remove(ticket);
            }

            return true;
        }
    }

    public static void LaunchProcess()
    {
        _thread = new Thread(new ThreadStart(delegate
                                                 {

                                                     while (true)
                                                     {
                                                         DoPass();
                                                     }
                                                 }));
        //    _thread.SetApartmentState(ApartmentState.STA);
        //   _thread.IsBackground = true;

        _thread.Start();
    }

    public static void DoPass()
    {
        try
        {
            Logger.WriteLine("DoPass enter");


            Dictionary<Delegate, int> copy;
            lock (actionsToRun)
            {
                copy = new Dictionary<Delegate, int>(actionsToRun);
            }


            //run
            foreach (var pair in copy)
            {
                object res = pair.Key.Method.Invoke(
                    pair.Key.Target, null);
                lock (results)
                {
                    results[pair.Value] = res;
                }
                lock (actionsToRun)
                {
                    actionsToRun.Remove(pair.Key);
                }

                Thread.Sleep(100);
            }
        }
        catch (Exception e)
        {
            Logger.WriteError(e);
            //mute
        }
    }
}

РЕДАКТИРОВАТЬ: ошибка может быть воспроизведена в простом тесте (readline просто там, чтобы дать время потоку ExcelConnector работать):

var excelApp = new Application();
        excelApp = new Application();
        excelApp.Visible = true;
        excelApp.DisplayAlerts = false;

        System.Action act = delegate
                                {
                                    string s = excelApp.Caption;
                                    Console.WriteLine(s);

                                };
        ExcelConnector.AddMethodToRun(act);
        Console.ReadLine();

Ответы [ 4 ]

5 голосов
/ 13 апреля 2011

К сожалению, нет смысла в том, что вы делаете, это уже делается.Взаимодействие Office основано на внепроцессном COM.Как и многие COM-интерфейсы, интерфейсы Excel помечены как разделы, внесенные в реестр.Это дорогой способ сказать, что они не поддерживают потоки.

COM автоматически заботится о компонентах, которые не поддерживают многопоточность, он автоматически маршалирует вызовы, сделанные в рабочем потоке, в поток, который создал объект COM,Это должен быть поток, который является STA, как основной поток любой программы, имеющей пользовательский интерфейс.При необходимости он автоматически создаст поток STA.Одним из побочных эффектов этого маршалинга является то, что вызовы рабочих потоков автоматически сериализуются.В конце концов, поток STA может отправлять только один вызов за раз.

Еще один побочный эффект заключается в том, что взаимоблокировка не является редкостью.Что произойдет, когда поток STA остается занятым и не качает цикл сообщений.Маршалинг выполняется с помощью программного кода COM, который использует петлю сообщений для отправки вызовов.Это условие довольно легко отладить, вы должны использовать Debug + Break All, Debug + Windows + Threads и проверить, чем занят поток STA (или Main).

Также остерегайтесь попыток такого рода потоковвероятно, это 90% причины, по которой вы получаете это исключение взаимодействия.Попытка получить код, который по сути потоково-небезопасен для выполнения более чем одной вещи одновременно, просто не работает.Вы бы избежали исключения «занято» из Excel, заблокировав свой собственный код, отметив операцию, которая переводит Excel в это состояние «занято», поэтому вы откладываете другие потоки.Больно конечно делать.

1 голос
/ 13 апреля 2011

Вы должны инициализировать COM в каждом потоке, который хотите использовать в своей библиотеке COM.Из документации CoInitializeEx" CoInitializeEx должен вызываться как минимум один раз и обычно вызывается только один раз для каждого потока, использующего библиотеку COM. ".

Вместо того, чтобы пытаться реализовать свою собственную многопоточность, возможно, вам следует проверить .NET Task Parallel Library.Проверьте этот вопрос на , используя COM-объекты из TPL .По сути, вы просто создаете объекты Task и отправляете их в StaTaskScheduler для выполнения.Планировщик управляет созданием и удалением потоков, вызывая исключения и т. Д.

0 голосов
/ 13 апреля 2011

Обычно не необходимо для инициализации COM в .NET - если вы не сделали некоторые нативные вещи, такие как P / Invoking.Использование IDispatch не требует явной инициализации COM.

Я думаю, что вы где-то просто заблокированы.Запустите ваш отладчик, когда он зависнет, взломайте и введите Monitor.TryEnter(...) для каждого объекта, который вы можете заблокировать (actionsToRun, results и другие).

Кстати, вам действительно следует подуматьреструктуризация вашего приложения, чтобы не использовать Excel одновременно - даже если вы делаете какую-то «сериализацию».Даже если вы заставите его работать, он вернется и укусит вас навсегда.Это официально не рекомендуется, и я говорю по опыту.

0 голосов
/ 13 апреля 2011

Я не очень разбираюсь в C #, но я предполагаю, что вам все равно нужно каким-то образом инициализировать COM при запуске нового потока, чтобы иметь окно сообщения, которое можно использовать для оповещения вашего потока после завершения операции.

...