Исключение COM для взаимодействия с Excel при работе в фоновом режиме

Я пытаюсь применить некоторые стили к ячейкам в моей книге. И я хочу сделать это в фоновом потоке, чтобы мой графический интерфейс оставался отзывчивым. Эта работа должна занять несколько секунд, и если я нажму на какую-нибудь случайную ячейку в своем документе, я получу исключение. Вот мой код:

public void ApplyStyles()
{
    BackgroundWorker bw = new BackgroundWorker();
    bw.DoWork += DoWork;
    bw.RunWorkerAsync();

}

private void DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        foreach (ICell xcell in cells)
        {
            Microsoft.Office.Interop.Excel.Range cell = cellUtility.GetCell(xcell);
            if (styles.ContainsKey(styleIds[xcell.Style]))
            {
                Style s = styles[xcell.Style];
                cell.Style = s;
            }
        }
    }
    catch (Exception ex)
    {
        if (Logger.IsErrorEnabled)
        {
            Logger.Error(ex.ToString());
        }
        messageBox.ShowErrorMessage(localizationMessages.ApplyingErrorText, localizationMessages.ApplyingErrorCaption);
    }
}

Когда происходит исключение, это сообщение, которое я получаю;

System.Runtime.InteropServices.COMException (0x800AC472): Exception from HRESULT: 0x800AC472
   at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)
   at Microsoft.Office.Interop.Excel.Range.set_Style(Object value)
   at ABZ.ReportFactory.OfficeAddin.Excel.BatchLinking.BackgroundStyleApplier.DoWork() in C:\ABZ\ABZ ReportFactory Office Addin\ABZ.ReportFactory.OfficeAddin.Excel\BatchLinking\BackgroundStyleApplier.cs:line 86

Можно ли выполнить этот стиль, применяя операцию в фоновом потоке? И как мне это сделать?

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

здравствуйте, Владимир


person Vajda    schedule 01.03.2012    source источник
comment
myomron.com/index.php?action=kb&article=1324   -  person Hans Passant    schedule 01.03.2012
comment
Итак, объект Excel виден, пока это происходит?   -  person SeanCocteau    schedule 01.03.2012


Ответы (1)


Вы должны быть уверены, что получаете корневой объект приложения Excel, а оттуда — диапазон из потока, в котором выполняется работа. Все COM-объекты Excel находятся в однопоточном апартаменте (STA), поэтому вы не можете просто использовать их из какого-то другого потока.

Вы не показываете, как «cellUtility.GetCell» на самом деле получает Range, но вероятная проблема заключается в том, что вы используете объект Application, который изначально был получен из другого потока.

Выполнение такой работы в «фоновом потоке, чтобы ваш графический интерфейс мог оставаться отзывчивым» проблематично — вся работа в Excel в конечном итоге выполняется в основном потоке Excel, поскольку Excel (по сути) является однопоточным.

Есть несколько подходов к этому:

  1. Часто вы обнаружите, что отключение ScreenUpdating и установка Calculation на Manual позволяет вам выполнять редактирование намного быстрее, тогда вам не нужны другие потоки или что-то еще.

  2. Запустите свою работу в основном потоке, но разбейте ее на небольшие фрагменты, а затем запланируйте выполнение следующего фрагмента после передачи в Excel — вы можете создать Windows.Forms.Timer или, если вы можете запустить макрос Excel, используйте Application.OnTime для запланировать следующую часть работы.

  3. Если вы хотите выполнять работу из другого потока, вам необходимо получить объект Application и дополнительный COM-объект в этом потоке. Получить правильный экземпляр приложения Excel может быть сложно, но Эндрю Уайтчепел описывает хороший подход здесь: http://blogs.officezealot.com/whitechapel/archive/2005/04/10/4514.aspx. Однако вам все равно придется проверять наличие ошибок при каждом вызове COM из другого потока, поскольку Excel может быть занят и может отклонить любой вызов COM из другого потока в любое время (особенно если ваши пользователи взаимодействуют с этот «отзывчивый графический интерфейс». Вам нужно хотя бы проверить COMExceptions с ошибками - каждый COM-вызов может вызвать одно из них:

    • const uint RPC_E_SERVERCALL_RETRYLATER = 0x8001010A;
    • константа VBA_E_IGNORE = 0x800AC472;

Excel-DNA (библиотека интеграции Excel/.NET, которую я разрабатываю) предоставляет доступ к объекту Application в потоке, который вы запускаются путем вызова ExcelDnaUtil.Application. Но я по-прежнему рекомендую перенести все взаимодействие с объектной моделью Excel в основной поток Excel, возможно, используя вызов Application.Run, чтобы запустить команду Excel для запуска макроса. Затем вызов Application.Run становится единственной точкой, в которой COMException может быть проверено и повторено из фонового потока.

person Govert    schedule 01.03.2012
comment
Мне кажется невозможным создать экземпляр приложения из другого потока, поскольку я использую для этого DI, и у меня есть только один экземпляр для каждого процесса. Возможно, я просто проигнорирую это исключение и попытаюсь повторить операцию, пока она не закончится. - person Vajda; 01.03.2012
comment
Не могли бы вы «внедрить зависимость» во вспомогательный объект с помощью метода для извлечения объекта Application в потоке, в котором он работает? Если вы действительно пытаетесь связаться с объектной моделью Excel через объект STA COM в неправильном потоке, вы находитесь в неподдерживаемой конфигурации. Даже если вы разместите приложение в правильном потоке, вам все равно придется проверять и обрабатывать эти ошибки. - person Govert; 01.03.2012