Вопрос по automation, excel, c# – автоматизация c # и excel - завершение работающего экземпляра

13

Я пытаюсь автоматизировать Excel через C #. Я следовал всем инструкциям Microsoft о том, как это сделать, но я все еще изо всех сил пытаюсь отбросить окончательную ссылку (ссылки) на Excel, чтобы она закрылась и чтобы GC мог ее собрать.

Пример кода приведен ниже. Когда я закомментирую блок кода, который содержит строки, похожие на:

<code>Sheet.Cells[iRowCount, 1] = data["fullname"].ToString();
</code>

Затем файл сохраняется и Excel выходит. В противном случае файл сохраняется, но Excel остается запущенным как процесс. В следующий раз, когда этот код запускается, он создает новый экземпляр, и они в конечном итоге собираются.

Любая помощь приветствуется. Благодарю.

Это скелет моего кода:

<code>        Excel.Application xl = null;
        Excel._Workbook wBook = null;
        Excel._Worksheet wSheet = null;
        Excel.Range range = null;

        object m_objOpt = System.Reflection.Missing.Value;

        try
        {
            // open the template
            xl = new Excel.Application();
            wBook = (Excel._Workbook)xl.Workbooks.Open(excelTemplatePath + _report.ExcelTemplate, false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt);
            wSheet = (Excel._Worksheet)wBook.ActiveSheet;

            int iRowCount = 2;

            // enumerate and drop the values straight into the Excel file
            while (data.Read())
            {

                wSheet.Cells[iRowCount, 1] = data["fullname"].ToString();
                wSheet.Cells[iRowCount, 2] = data["brand"].ToString();
                wSheet.Cells[iRowCount, 3] = data["agency"].ToString();
                wSheet.Cells[iRowCount, 4] = data["advertiser"].ToString();
                wSheet.Cells[iRowCount, 5] = data["product"].ToString();
                wSheet.Cells[iRowCount, 6] = data["comment"].ToString();
                wSheet.Cells[iRowCount, 7] = data["brief"].ToString();
                wSheet.Cells[iRowCount, 8] = data["responseDate"].ToString();
                wSheet.Cells[iRowCount, 9] = data["share"].ToString();
                wSheet.Cells[iRowCount, 10] = data["status"].ToString();
                wSheet.Cells[iRowCount, 11] = data["startDate"].ToString();
                wSheet.Cells[iRowCount, 12] = data["value"].ToString();

                iRowCount++;
            }

            DirectoryInfo saveTo = Directory.CreateDirectory(excelTemplatePath + _report.FolderGuid.ToString() + "\\");
            _report.ReportLocation = saveTo.FullName + _report.ExcelTemplate;
            wBook.Close(true, _report.ReportLocation, m_objOpt);
            wBook = null;

        }
        catch (Exception ex)
        {
            LogException.HandleException(ex);
        }
        finally
        {
            NAR(wSheet);
            if (wBook != null)
                wBook.Close(false, m_objOpt, m_objOpt);
            NAR(wBook);
            xl.Quit();
            NAR(xl);
            GC.Collect();
        }

private void NAR(object o)
{
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(o);
    }
    catch { }
    finally
    {
        o = null;
    }
}
</code>
Update

Независимо от того, что я пытаюсь, «чистый» способ или «уродливый»; метод (см. ответы ниже), экземпляр Excel по-прежнему зависает, как только эта строка нажата:

<code>wSheet.Cells[iRowCount, 1] = data["fullname"].ToString();
</code>

Если я прокомментирую эту строку (и другие аналогичные ниже, очевидно), приложение Excel завершится изящно. Как только одна строка выше не комментируется, Excel остается.

Я думаю, что мне нужно будет проверить, есть ли работающий экземпляр, до назначения переменной xl, и вместо этого подключиться к ней. Я забыл упомянуть, что это служба Windows, но это не имеет значения, не так ли?

Хорошая находка - и вы правы, я уже давно перестал задумываться об этом :) Я прочту эту ссылку, спасибо. Всегда хорошо продолжать учиться. Sean
Если вы когда-нибудь задумывались об этом (в чем я серьезно сомневаюсь, и надеюсь, что нет), я наткнулсяthis post by Hans Passant который (а) объясняет почемуGC.Collect является правильным способом сделать это, и (б) почему это не можетlook как будто это работаетwhen running in the debugger, Если бы только у нас был Ганс, чтобы помочь всем этим много лет назад :-) Gary McGill
Я только что заметил, что вы упомянули, что он работает как служба Windows. Это не должно иметь никакого отношения к данному вопросу, но позвольте мне рассказать вам об этом. Мое приложение (или его часть) работает под управлением службы Windows и работает нормально под Windows XP или Windows 2003 Server, но у меня проблемы с ним в Vista, и я не могу запустить его под Windows Server 2008. Просто подумал Я бы упомянул, что если ваша машина для разработки - XP, а ваша целевая платформа - Vista / 2008 ... Gary McGill
Я должен упомянуть (хотя это не особенно важно для обсуждения), что ваш метод NAR, вероятно, не выполняет то, что вы думаете. Строка & quot; o = null; & quot; в блоке finally устанавливается переменная локального параметра o в значение null, но это не влияет на переменную в вызывающем методе. Поэтому после NAR (xl) переменная xl НЕ будет нулевой! Сказав это, не должно быть необходимости устанавливать переменные равными нулю в любом случае - если вы не суеверны в этом. Gary McGill
Вы не получите "некрасивый" метод работы, пока вы не получите это правильно. Здесь нет полумер, потому что до тех пор, покаany Ссылка на COM не была опубликована, Excel останется без изменений. Пожалуйста, опубликуйте свой "уродливый" код, чтобы мы могли обнаружить ошибку - я могу гарантировать, что она будет. Обратите внимание, что в опубликованном коде вы устанавливаете для wb значение null в главном блоке и, таким образом,preventing это от того, чтобы быть должным образом выпущенным в вашем, наконец, блоке. Но это только одна проблема (мы обсуждали объект Workbooks и объекты Range ниже). Gary McGill

Ваш Ответ

14   ответов
6

Sheet.Cells[iRowCount, 1] = data["fullname"].ToString();

По сути то же самое, что и:

Excel.Range cell = Sheet.Cells[iRowCount, 1];
cell.Value = data["fullname"].ToString();

Делая это таким образом, вы можете увидеть, что вы создаетеExcel.Range объект, а затем присваивая ему значение. Этот способ также дает нам именованную ссылку на нашу переменную диапазона,cell переменная, которая позволяет нам выпустить его напрямую, если мы хотим. Таким образом, вы можете очистить ваши объекты одним из двух способов:

(1) Сложный и безобразный путь:

while (data.Read())
{
    Excel.Range cell = Sheet.Cells[iRowCount, 1];
    cell.Value = data["fullname"].ToString();
    Marshal.FinalReleaseComObject(cell);

    cell = Sheet.Cells[iRowCount, 2];
    cell.Value = data["brand"].ToString();
    Marshal.FinalReleaseComObject(cell);

    cell = Sheet.Cells[iRowCount, 3];
    cell.Value = data["agency"].ToString();
    Marshal.FinalReleaseComObject(cell);

    // etc...
}

Выше мы освобождаем каждый объект диапазона посредством вызоваMarshal.FinalReleaseComObject(cell) как мы идем вместе.

(2) простой и чистый способ:

Оставьте свой код в точности так, как он у вас есть, и в конце вы можете очистить его следующим образом:

GC.Collect();
GC.WaitForPendingFinalizers();

if (wSheet != null)
{
    Marshal.FinalReleaseComObject(wSheet)
}
if (wBook != null)
{
    wBook.Close(false, m_objOpt, m_objOpt);
    Marshal.FinalReleaseComObject(wBook);
}
xl.Quit();
Marshal.FinalReleaseComObject(xl);

Короче говоря, ваш существующий кодextremely close, Если вы просто добавляете вызовы в GC.Collect () и GC.WaitForPendingFinalizers () перед вашим & quot; NAR & reg; звонки, я думаю, это должно работать для вас. (Короче говоря, и код Jamie, и код Ahmad верны. Jamie более понятен, но код Ahmad проще для вас, потому что вам нужно будет только добавить вызовы к вызовам GC.Collect () и GC.WaitForPendingFinalizers () в существующий код.)

Джейми и Амхад также перечислили ссылки на.NET Automation Forum в котором я участвую (спасибо, ребята!) Вот пара похожих постов, которые я сделал здесьПереполнение стека:

(1) H, должным образом очистить объекты взаимодействия Excel в C #

(2) C # Автоматизация PowerPoint Excel - PowerPoint не выходит

Надеюсь, это поможет, Шон ...

Майк

Прямо из источника :)
лол, спасибо за пробку, Ахмад. :-)
Отлично, спасибо - я попытался найти в стеке поток для этого. Очевидно, я недостаточно хорошо сформулировал свой поиск, чтобы найти твои сообщения, Майк. Спасибо, что нашли время, чтобы заново объяснить. Я понимаю о скрытых ссылках и о том, почему их не убирали, но я не смог найти образец, который показал бы "уродливые". путь в сети. Я попробую все это этим вечером. Я отмечу, что в этот момент ответили. (Я в Ирландии, поэтому различие во времени играет хаос!). Спасибо всем, кто также выручил, выше. Sean
... Поэтому, с практической точки зрения, я считаю, что путь к заключительной части очистки одного кода для вызова GC.Collect () и GC.WaitForPendingFinaliers с последующим выпуском всех именованных переменных ссылки с использованием Marshal.FinalReleaseComObject () для каждой ссылки, одна за другой.
Шон, удачи в этом, и дай нам знать, как это происходит. Я опубликовал «уродливый путь» только чтобы объяснить, что происходит "под капотом". Вы можете использовать его в качестве эксперимента, чтобы помочь вам понять, как все это работает, но я бы не стал использовать его для реального производственного кода. Причина в том, что слишком легко сделать одну маленькую ошибку где-то в вашем коде, и Excel зависнет. Ни компилятор, ни какая-либо программа анализа кода не смогут найти эту ошибку за вас. Этот подход также увеличивает ваш код большим количеством обращений к Marshal.FinalReleaseComObject (), отвлекая от намерений одного кода. (Продолжение ...)
0

У Гэри могут быть некоторые мысли, но, хотя его подход обертки очень тверд, он в действительности не поможет вам в этом случае, потому что вы уже все делаете в значительной степени правильно.

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

xlrange.Value2 = "fullname";

Казалось бы, не повлияет ни одна из этих идей, но здесь идет:

(1) Не используйте интерфейсы _Workbook и _Worksheet. Вместо этого используйте Workbook и Worksheet. (Подробнее об этом см .:Взаимодействие с Excel: _Worksheet или Worksheet?.)

(2) Каждый раз, когда у вас есть две точки (& quot;. & Quot;) на одной строке при доступе к объекту Excel, разбивайте его на две строки, назначая каждый объект именованной переменной. Затем в разделе очистки вашего кода явно освободите каждую переменную с помощью Marshal.FinalReleaseComObject ().

Например, ваш код здесь:

 wBook = (Excel._Workbook)xl.Workbooks.Open(@"E:\Development\Romain\APN\SalesLinkReportManager\ExcelTemplates\DailyProposalReport.xls", false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt);

может быть разбит на:

Excel.Workbooks wBooks = xl.Workbooks;
wBook = wBooks.Open("@"E:\Development\...\DailyProposalReport.xls", etc...);

А потом, в разделе очистки, вы получите:

Marshal.FinalReleaseComObject(xlrange);
Marshal.FinalReleaseComObject(wSheet);
Marshal.FinalReleaseComObject(wBook);
Marshal.FinalReleaseComObject(wBooks); // <-- Added
Marshal.FinalReleaseComObject(xl);

(3) Я не уверен, что происходит с вашим подходом Process.Kill. Если вы вызываете wBook.Close (), а затем xl.Quit () перед вызовом Process.Kill (), у вас не должно быть проблем. Workbook.Close () не возвращает выполнение до тех пор, пока книга не будет закрыта, а Excel.Quit () не вернет выполнение до тех пор, пока Excel не завершит работу (хотя он все еще может зависать).

После вызова Process.Kill () вы можете проверить свойство Process.HasExited в цикле или, что еще лучше, вызвать метод Process.WaitForExit (), который будет приостанавливаться до тех пор, пока он не завершится для вас. Я предполагаю, что на это обычно уйдет меньше секунды. Лучше ждать меньше времени и быть уверенным, чем ждать 5 - 10 секунд и только догадываться.

(4) Вам следует попробовать эти идеи по очистке, которые я перечислил выше, но я начинаю подозревать, что у вас могут возникнуть проблемы с другими процессами, которые могут работать с Excel, такими как надстройка или антивирусная программа. Эти надстройки могут привести к зависанию Excel, если они выполнены неправильно. Если это произойдет, может быть очень трудно или невозможно заставить Excel выпустить. Вам нужно будет найти программу-нарушителя и затем отключить ее. Другая возможность заключается в том, что работа в качестве службы Windows как-то является проблемой. Я не понимаю, почему это так, но у меня нет опыта автоматизации Excel через службу Windows, поэтому я не могу сказать. Если ваши проблемы связаны с этим, то использование Process.Kill, скорее всего, будет вашим единственным средством здесь.

Это все, что я могу придумать, Шон. Надеюсь, это поможет. Дайте нам знать, как это идет ...

- Майк

Тьфу ... Очень жаль слышать обо всем этом, Шон. Это неsuposed быть таким трудным Но это случается. :-(
Снова Майк, спасибо за исчерпывающий ответ и помощь. Что касается 1) и 2) выше - на этом этапе я попробовал все эти и многие другие перестановки, пытаясь решить эту проблему! Я даже создал новый поток для обработки работы в Excel на случай, если есть какая-то основная проблема, но нет радости. Что показалось мне странным, так это то, что именно файл windowsService.exe хранит дескриптор файла, не позволяя мне удалить его, пока я не выйду из самой службы. Я мог бы прибегнуть к xml Excel, чтобы сделать это сейчас. Сначала я попробую предложения выше. Еще раз спасибо. Sean
Далее в 3) выше, при отладке кода wBook.Close и xl.Quit возвращаются быстро и без ошибок. После этого вызывается process.kill, но файл все еще остается в открытом состоянии. Я могу открыть файл, но получаю сообщение о том, что файл уже открыт, и мне приходится выбирать «Только чтение» или «Уведомлять» даже после уничтожения осиротевшего файла Excel.exe. Более странно, чем выдумка - может быть, мне нужно переписать текст, если это что-то очевидно, и я просто слишком долго на него смотрел. Вздох. Sean
12

UPDATE (November 2016)

Я только что прочиталубедительный аргумент Ганс Пассант, который используетGC.Collect на самом деле правильный путь. Я больше не работаю с Office (слава богу), но если бы я это сделал, я, вероятно, хотел бы попробовать еще раз - это, безусловно, упростило бы большую часть (тысяч строк) кода, который я написал, пытаясь сделать то, что & quot; право & Quot; кстати (как я тогда это увидел).

Я оставлю свой первоначальный ответ для потомков ...

Как говорит Майк в своем ответе, есть простой и трудный способ справиться с этим. Майк предлагает использовать простой способ, потому что ... это проще. Я лично не считаю, что это достаточно веская причина, и я не верю, что это правильный путь. Это пахнет "выключить и снова включить" мне.

У меня есть многолетний опыт разработки приложений автоматизации Office в .NET, и эти проблемы взаимодействия COM мучили меня в течение первых нескольких недель & amp; месяцы, когда я впервые столкнулся с этой проблемой, не в последнюю очередь из-за того, что Microsoft очень стесняется признать в первую очередь проблему, а в то время в Интернете было сложно найти хороший совет.

У меня есть способ работы, который я сейчас использую практически, не думая об этом, и вот уже много лет, как у меня возникла проблема. По-прежнему важно быть живым со всеми скрытыми объектами, которые вы, возможно, создаете - и да, если вы пропустите один из них, у вас может быть утечка, которая станет очевидной гораздо позже. Но это не хуже, чем было в старые добрые временаmalloc/free.

Я действительно думаю, что нужно что-то сказать о том, чтобы убирать за собой по ходу дела, а не в конце. Если вы только запускаете Excel, чтобы заполнить несколько ячеек, то, возможно, это не имеет значения, но если вы собираетесь выполнять какую-то тяжелую работу, то это другой вопрос.

Во всяком случае, техника, которую я использую, заключается в использовании класса-обертки, который реализуетIDisposableи который в своемDispose вызовы методовReleaseComObject, Таким образом, я могу использоватьusing операторы, обеспечивающие удаление объекта (и освобождение объекта COM), как только я закончу с ним.

Важно то, что он будет удален / освобожден, даже если моя функция вернется рано или произойдет исключение и т. Д. Кроме того, он будетonly избавиться / освободиться, если он был изначально создан - назовите меня педантом, но предлагаемый код, который пытается выпустить объекты, которые, возможно, не были созданы, выглядит для меня как неаккуратный код. У меня есть подобное возражение против использования FinalReleaseComObject - вы должны знать, сколько раз вы вызывали создание ссылки COM, и поэтому должны иметь возможность выпускать его такое же количество раз.

Типичный фрагмент моего кода может выглядеть так (или будет, если бы я использовал C # v2 и мог бы использовать обобщенные значения :-)):

using (ComWrapper<Excel.Application> application = new ComWrapper<Excel.Application>(new Excel.Application()))
{
  try
  {
    using (ComWrapper<Excel.Workbooks> workbooks = new ComWrapper<Excel.Workbooks>(application.ComObject.Workbooks))
    {
      using (ComWrapper<Excel.Workbook> workbook = new ComWrapper<Excel.Workbook>(workbooks.ComObject.Open(...)))
      {
        using (ComWrapper<Excel.Worksheet> worksheet = new ComWrapper<Excel.Worksheet>(workbook.ComObject.ActiveSheet))
        {
          FillTheWorksheet(worksheet);
        }
        // Close the workbook here (see edit 2 below)
      }
    }
  }
  finally
  {
    application.ComObject.Quit();
  }
}

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

Обратите внимание, что в соответствии с приведенным выше примером я всегда пропускаю 'обернутый' объекты между методами, а не голый COM-объект, и вызывающая сторона должна была бы избавиться от него (обычно сusing заявление). Точно так же я всегда возвращал бы завернутый объект, а не голый, и опять же вызывающая сторона должна была бы освободить его. Вы могли бы использовать другой протокол, но важно иметь четкие правила, как это было раньше, когда нам приходилось делать свое собственное управление памятью.

ComWrapper<T> Надеюсь, что используемый здесь класс требует небольшого объяснения. Он просто хранит ссылку на обернутый COM-объект и освобождает его явно (используяReleaseComObject) в своемDispose метод.ComObject Метод просто возвращает типизированную ссылку на обернутый COM-объект.

Надеюсь это поможет!

EDIT: Я только сейчас перешел по ссылке наОтвет Майка на другой вопроси я вижу, что в другом ответе на этот вопрос есть ссылка на класс-оболочку, как я и предлагал выше.

Кроме того, что касается ответа Майка на этот другой вопрос, я должен сказать, что меня едва не соблазнила просто использоватьGC.Collect& Quot; аргумент. Тем не менее, я был в основном привлечен к этому на ложной предпосылке; На первый взгляд все выглядело так, как будто бы вообще не нужно беспокоиться о ссылках на COM. Однако, как говорит Майк, вам все еще нужно явно освобождать COM-объекты, связанные со всеми вашими переменными в области действия, и поэтому все, что вы сделали, - это уменьшили, а не устранили необходимость в управлении COM-объектами. Лично я предпочел бы пойти на всю свинью.

Я также отмечаю тенденцию во многих ответах писать код, где все освобождается в конце метода, в большом блокеReleaseComObject звонки. Это все очень хорошо, если все работает, как запланировано, но я призываю любого, кто пишет серьезный код, подумать, что произойдет, если будет сгенерировано исключение, или если у метода будет несколько точек выхода (код не будет выполнен, и, следовательно, COM-объекты не будут выпущены). Вот почему я предпочитаю использовать «обертки» а такжеusings. Это многословно, но это делает для пуленепробиваемого кода.

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

object saveChanges = Excel.XlSaveAction.xlSaveChanges;

workbook.ComObject.Close(saveChanges, Type.Missing, Type.Missing);

... и кnot сохранить изменения, просто изменитьxlSaveChanges вxlDoNotSaveChanges.

Майк, убивая такой процесс, может работать в 99,99% случаев, рано или поздно его убьют, когда он делает что-то, что нельзя оставить наполовину завершенным. Например, если бы это был Word, а не Excel, он мог бы записывать изменения в Normal.dot ... Уничтожение процесса также не дает ему возможности убирать за собой; у нас был пример этого в этой самой теме с файлом, который оставляли доступным только для чтения после уничтожения Excel.
Спасибо обоим за отличную дискуссию. Я привлечен идеей обертки (также пригодной для повторного использования кода) и попробую ее на выходных. Я опубликую свой код сегодня вечером, если у меня будет возможность, хотя я подозреваю, что выходные дни в StackOverflow несколько более спокойные - или, скорее, они должны быть :) Sean
У меня также никогда не возникало проблем с использованием подходов GC.Collect () и GC.WaitForPendingFinalizers (), и я помогал бесчисленным кодировщикам в & lt; a href = & quot;xtremevbtalk.com/forumdisplay.php?f=105">XVBT .NET Automation & lt; / a & gt; Форум. Это может быть первым, хотя ... :-(
Очень мило, Гари, мне это очень нравится. В качестве компромисса можно обойтись только одной оболочкой только для экземпляра Excel.Application. Затем в рамках процедуры Dispose для оболочки Excel.Application можно было бы использовать подход Process.Kill, который получает идентификатор процесса с помощью Application.Hwnd и GetWindowThreadProcessId (stackoverflow.com/questions/51462/…). По сути, это то, что я делаю для своего производственного кода, и у меня не было проблем. Это экономит массу вложенности, которую вы показываете, поэтому код протекает гораздо более естественно.
Действительно, именно поэтому я и попросил его показать свой код. Это абсолютно не должно происходить и не имеет никакого отношения к «висящему экземпляру». В заключительном разделе очистки кода необходимо проверить, есть ли «Рабочая книга»; является нулевым, если нет, закройте его, затем вызовите Marshall.FinalReleaseComObject (), затем вызовите Excel.Application.Quit (), а затем Process.Kill. Это поднимает вас с 99,99% до 100%. Но нам нужно увидеть его код.
0

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

private bool GenerateDailyProposalsReport(ScheduledReport report)
{
    // start of test

    Excel.Application xl = null;
    Excel._Workbook wBook = null;
    Excel._Worksheet wSheet = null;
    Excel.Range xlrange = null;
    object m_objOpt = System.Reflection.Missing.Value;

    xl = new Excel.Application();
    wBook = (Excel._Workbook)xl.Workbooks.Open(@"E:\Development\Romain\APN\SalesLinkReportManager\ExcelTemplates\DailyProposalReport.xls", false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt);
    wSheet = (Excel._Worksheet)wBook.ActiveSheet;
    xlrange = wSheet.Cells[2, 1] as Excel.Range;

    // PROBLEM LINE ************
    xlrange.Value2 = "fullname";
    //**************************

    wBook.Close(true, @"c:\temp\DailyProposalReport.xls", m_objOpt);
    xl.Quit();

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    GC.WaitForPendingFinalizers();

    Marshal.FinalReleaseComObject(xlrange);
    Marshal.FinalReleaseComObject(wSheet);
    Marshal.FinalReleaseComObject(wBook);
    Marshal.FinalReleaseComObject(xl);

    xlrange = null;
    wSheet = null;
    wBook = null;
    xl = null;

    // end of test
    return true;

}

Если я закомментирую строку «ПРОБЛЕМА» выше, экземпляр Excel будет освобожден из памяти. Как оно есть, это не так. Я был бы признателен за любую дополнительную помощь в этом, поскольку время мимолетно и приближается крайний срок (не все они).

Пожалуйста, спросите, если вам нужна дополнительная информация. Спасибо в ожидании.

Addendum

Немного больше информации, которая может или не может пролить больше света на это. Я прибег к уничтожению процесса (измерение временной задержки) по истечении определенного промежутка времени (5-10 секунд, чтобы дать Excel время завершить его процессы). У меня запланировано два отчета - первый отчет создается и сохраняется на диск, а процесс Excel завершается, а затем отправляется по электронной почте. Второй создается, сохраняется на диск, процесс останавливается, но внезапно возникает ошибка при попытке электронной почты. Ошибка:  Процесс не может получить доступ к файлу "..." так далее

Таким образом, даже если приложение Excel было уничтожено, фактический файл Excel все еще хранится службой Windows. Я должен убить службу, чтобы удалить файл ...

,
0

SpreadsheetGear для .NET? Вот что может понравиться вашему коду с помощью SpreadsheetGear:

// open the template            
using (IWorkbookSet workbookSet = SpreadsheetGear.Factory.GetWorkbookSet())
{
    IWorkbook wBook = workbookSet.Workbooks.Open(excelTemplatePath + _report.ExcelTemplate);
    IWorksheet wSheet = wBook.ActiveWorksheet;
    int iRowCount = 2;
    // enumerate and drop the values straight into the Excel file            
    while (data.Read())
    {
        wSheet.Cells[iRowCount, 1].Value = data["fullname"].ToString();
        wSheet.Cells[iRowCount, 2].Value = data["brand"].ToString();
        wSheet.Cells[iRowCount, 3].Value = data["agency"].ToString();
        wSheet.Cells[iRowCount, 4].Value = data["advertiser"].ToString();
        wSheet.Cells[iRowCount, 5].Value = data["product"].ToString();
        wSheet.Cells[iRowCount, 6].Value = data["comment"].ToString();
        wSheet.Cells[iRowCount, 7].Value = data["brief"].ToString();
        wSheet.Cells[iRowCount, 8].Value = data["responseDate"].ToString();
        wSheet.Cells[iRowCount, 9].Value = data["share"].ToString();
        wSheet.Cells[iRowCount, 10].Value = data["status"].ToString();
        wSheet.Cells[iRowCount, 11].Value = data["startDate"].ToString();
        wSheet.Cells[iRowCount, 12].Value = data["value"].ToString();
        iRowCount++;
    }
    DirectoryInfo saveTo = Directory.CreateDirectory(excelTemplatePath + _report.FolderGuid.ToString() + "\\");
    _report.ReportLocation = saveTo.FullName + _report.ExcelTemplate;
    wBook.SaveAs(_report.ReportLocation, FileFormat.OpenXMLWorkbook);
}

Если у вас есть несколько строк, вы можете быть шокированы тем, насколько быстрее он работает. И вам никогда не придется беспокоиться о зависании экземпляра Excel.

Вы можете скачать бесплатную пробную версиюВот и попробуй сам.

Отказ от ответственности: я владею SpreadsheetGear LLC

Спасибо, Джо. Если я получу намного больше работы в Excel и / или более высокооплачиваемом клиенте, я могу потратить время на его оценку. На данный момент это небольшой проект, и маршрут, который я выбираю, кажется наиболее жизнеспособным. Я буду иметь это в виду, хотя. Sean
1
1

как это было опубликовано, но я столкнулся с той же проблемой и смог ее решить. Очевидно, что простой доступ к массиву Cells создает COM-объект. Так что, если вы должны были сделать:

    wSheet = (Excel._Worksheet)wBook.ActiveSheet;
    Microsoft.Office.Interop.Excel.Range cells = wSheet.Cells;

    int iRowCount = 2;

    // enumerate and drop the values straight into the Excel file
    while (data.Read())
    {
        Microsoft.Office.Interop.Excel.Range cell = cells[iRowCount, 1];
        cell  = data["fullname"].ToString();
        Marshal.FinalReleaseComObject(cell);
    }
    Marshal.FinalReleaseComObject(cells);

а затем остальная часть вашей очистки должна решить проблему.

0

Я собираюсь повторно опубликовать ваш код снова с моими изменениями (ниже). Я избегал слишком частого изменения кода, поэтому не добавил обработки исключений и т. Д. Этот кодnot надежный.

private bool GenerateDailyProposalsReport(ScheduledReport report)
{
    Excel.Application xl = null;
    Excel.Workbooks wBooks = null;
    Excel.Workbook wBook = null;
    Excel.Worksheet wSheet = null;
    Excel.Range xlrange = null;
    Excel.Range xlcell = null;

    xl = new Excel.Application();

    wBooks = xl.Workbooks;

    wBook = wBooks.Open(@"DailyProposalReport.xls", false, false, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

    wSheet = wBook.ActiveSheet;

    xlrange = wSheet.Cells;

    xlcell = xlrange[2, 1] as Excel.Range;

    xlcell.Value2 = "fullname";

    Marshal.ReleaseComObject(xlcell);
    Marshal.ReleaseComObject(xlrange);
    Marshal.ReleaseComObject(wSheet);

    wBook.Close(true, @"c:\temp\DailyProposalReport.xls", Type.Missing);

    Marshal.ReleaseComObject(wBook);
    Marshal.ReleaseComObject(wBooks);

    xl.Quit();

    Marshal.ReleaseComObject(xl);

    return true;
}

Обратите внимание:

The Workbooks method of the Application class creates a Workbooks object which holds a reference to the corresponding COM object, so we need to ensure we subsequently release that reference, which is why I added the variable wBooks and the corresponding call to ReleaseComObject.

Similarly, the Cells method of the Worksheet object returns a Range object with another COM reference, so we need to clean that up too. Hence the need for 2 separate Range variables.

I've released the COM references (using ReleaseComObject) as soon as they're no longer needed, which I think is good practice even if it isn't strictly necessary. Also, (and this may be superstition) I've released all the objects owned by the workbook before closing the workbook, and released the workbook before closing Excel.

I'm not calling GC.Collect etc. because it shouldn't be necessary. Really!

I'm using ReleaseComObject rather than FinalReleaseComObject, because it should be perfectly sufficient.

I'm not null-ing the variables after use; once again, it's not doing anything worthwhile.

Not relevant here, but I'm using Type.Missing instead of System.Reflection.Missing.Value for convenience. Roll on C#v4 where optional parameters will be supported by the compiler!

Я не смог скомпилировать или запустить этот код, но я уверен, что он сработает.Good luck!

Шон, я думаю, это может быть связано с работой через службу Windows, я не понимаю, почему это может быть, но у меня нет опыта в этом, поэтому я не знаю. Это также может быть надстройка, работающая с Excel или антивирусом или тому подобное, которая вызывает зависание Excel - я видел это сам. Что бы это ни было, это что-то странное ... Извините, это сработало для вас. :-(
Шон, мое приложение работает как служба Windows без проблем, поэтому, хотя это, безусловно, является дополнительным осложнением, я бы не стал воспринимать это как ограничитель показа. Мне жаль, что мы не смогли заставить это работать. Я был бы готов придерживаться этого немного дольше, но я вполне могу понять, что вы уже сыт по горло этим.
Привет Гэри - спасибо за это, код был в порядке, за исключением одного отсутствующего приведения. Во всяком случае, код работал, но все равно не очистил Excel после себя. Я потерял желание продолжать хороший бой, и мне придется прибегнуть к Open Xml, чтобы создать и сохранить файл xls. Как говорит MS: Microsoft не рекомендует и не поддерживает серверную автоматизацию Office. Теперь я понимаю, почему! Это должно быть связано с тем, что он запускается через службу Windows. Спасибо всем за помощь - особенно Майк и Гэри. Приятно знать, что есть люди, готовые помочь разработчикам. Sean
Хороший список точек, Гэри, и хороший пикап на «Ячейках»; метод, который мог бы быть билетом ... Странная вещь в том, что код Seans зависает не от строки «xlrange = wSheet.Cells [2, 1] как Excel.Range», а от добавления строки «xlrange». Value2 = "полное имя", что не имеет никакого смысла.
Спасибо за проверку этого Гэри. В этом случае вы убрали последний возможный кусок соломинки, за который мне пришлось цепляться :) Я начал использовать XML, поскольку это всего лишь пара отчетов с минимальным форматированием, которые мне нужно создать. , Если я получу возможность провести повторное расследование позже, я это сделаю. Спасибо еще раз за помощь. Кроме того, я использую PIA 2007 на Windows XP - я полагаю, что вы, ребята, тоже на 2007 без проблем ... Sean
2

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

Вы также можете использоватьMarshal.FinalReleaseComObject() в вашем методе NAR вместо ReleaseComObject.ReleaseComObject уменьшает счетчик ссылок на 1, аFinalReleaseComObject освобождает все ссылки, поэтому счетчик равен 0.

Таким образом, ваш блок finally будет выглядеть так:

finally
{
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 

    NAR(wSheet);
    if (wBook != null)
        wBook.Close(false, m_objOpt, m_objOpt);
    NAR(wBook);
    xl.Quit();
    NAR(xl);
}

Обновленный метод NAR:

private void NAR(object o)
{
    try
    {
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(o);
    }
    catch { }
    finally
    {
        o = null;
    }
}

Я исследовал это некоторое время назад, и в примерах я обнаружил, что звонки, связанные с GC, были в конце после закрытия приложения. Однако существует MVP (Mike Rosenblum), в котором упоминается, что его следует вызывать в начале. Я пробовал оба способа, и они работали. Я также попробовал это без WaitForPendingFinalizers, и это работало, хотя это ничего не должно повредить. YMMV.

Вот соответствующие ссылки MVP, о которых я упоминал (они в VB, но они не так уж и отличаются):

http://www.xtremevbtalk.com/showthread.php?p=1157109#post1157109 http://www.xtremevbtalk.com/showthread.php?s=bcdea222412c5cbfa7f02cfaf8f7b33f&p=1156479#post1156479
Привет Гэри, я согласен, что ты неhave сделать это таким образом, но на практике я обнаружил, что слишком легко пропустить один (или несколько) явных вызовов объекта Marshal.FinalReleaseCom (), и тогда вы повесите трубку. Найти вашу ошибку в средне-крупномасштабном проекте может быть практически невозможно. (Ни компилятор, ни инструменты анализа кода не помогут вам.) Поэтому я считаю, что гораздо безопаснее использовать «окончательную очистку» & quot; подход.
Майк, это интересно, что GC x 2 не требуется вне VSTO, спасибо.
Да, двойной звонок тоже был для меня новым, и это приятно знать.
Гэри, вам не нужно вызывать GC.Collect () и GC.WaitForPendingFinalizers () дважды. Что происходит, когда вы вызываете GC.Collect (), все финализаторы для RCW вызываются, что уменьшает счетчик ссылок COM и освобождает ссылку на стороне COM ограждения (это все, что вам нужно здесь). Сам RCW, однако, останется без сбора до следующей сборки мусора. Но нас это не волнует - RCW уже сбросил полезную нагрузку. (Однако, если вы используете VSTO, вы ДОЛЖНЫ дважды вызывать Collect / Wait, потому что VSTO использует дополнительный уровень финализаторов.)
Использование сборщика мусора таким способом не должно быть необходимым. У меня есть чрезвычайно сложная система автоматизации Office, написанная на .NET, и я признаю, что в одном случае мне пришлось прибегнуть к этому, но для любого небольшого и среднего проекта вы должны быть в состоянии очистить ссылки COM так, что это не обязательно. Обратите внимание, что если вы используете этот метод, вам нужно выполнить Collect / Wait дважды, так как первая итерация просто помечает вещи как доступные для сбора.
0

ользовать OleDb для изменения листов.

http://www.codeproject.com/KB/office/excel_using_oledb.aspx

0

и, наконец, наконец, для очистки автоматизации Excel. Мое приложение оставляет Excel открытым, поэтому нет выхода из системы.ссылка в комментарии был мой источник.

finally
{
    // Cleanup -- See http://www.xtremevbtalk.com/showthread.php?t=160433
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    GC.WaitForPendingFinalizers();
    // Calls are needed to avoid memory leak
    Marshal.FinalReleaseComObject(sheet);
    Marshal.FinalReleaseComObject(book);
    Marshal.FinalReleaseComObject(excel);
}
0

что я закончил делать, чтобы решить подобную проблему, было получить идентификатор процесса и убить его как последнее средство ...

[DllImport("user32.dll", SetLastError = true)]
   static extern IntPtr GetWindowThreadProcessId(int hWnd, out IntPtr lpdwProcessId);

...

objApp = new Excel.Application();

IntPtr processID;
GetWindowThreadProcessId(objApp.Hwnd, out processID);
excel = Process.GetProcessById(processID.ToInt32());

...

objApp.Application.Quit();
Marshal.FinalReleaseComObject(objApp);
_excel.Kill();
О, господин, не делай этого (убей процесс). Очистка ваших ссылок COM является болью, но очень выполнимой. Завершение процесса - все равно что застрелить твою нелюбимую подругу, чтобы не порвать с ней.
Гэри - возможно, выполнимо, но в моем случае совсем не надежно. Все остальные методы оставили меня с запущенными процессами (20-30 из них) после часа или около того выполнения.
Привет, Энди, я прибег к твоему методу. Однако, даже если экземпляр Excel уничтожен, у меня все еще остается файл Excel, который считает, что к нему обращается другое приложение. При открытии файла мне выдается сообщение «Уведомить / только для чтения». Sean
0

_worksheet а также_workbook и все было хорошо.

Что вы имеете в виду, что вы удалили их?
2

я бы предложил, чтобы, если вы имеете дело с файлами Excel с расширением XLSX, вы должны использоватьEPPlus что сделает ваши кошмары Excel уйти.

Похожие вопросы