COMException when using MailItem.Delete()

L

LeonKh

Hi,

I would appreciate any ideas about avoiding the following exception:
----
[ERR] System.Runtime.InteropServices.COMException (0x80040119):
One or more items cannot be moved because items you previously moved are
still being processed by the server.
[ERR] at Microsoft.Office.Interop.Outlook._MailItem.Delete()
----

I am getting this exception when deleting the first email (NB: I am starting
deletion from the last item in MAPIFolder.Items) in Sent Item folder right
after deleting all emails in Inbox is finished. Usually this problem doesn't
happen right after I am starting my automation, but after it happens once it
will happen in all subsequent runs. Also, it never happens with Outlook2k3,
but it does with Outlook2k7.

Thank you,
--Leon.
 
K

Ken Slovak - [MVP - Outlook]

Show the code loop you're using.

You are releasing all of your objects? Often due to GC latency you will need
to call Marshal.ReleaseComObject() or Marshal.FinalReleaseComObject().
Setting the objects to null isn't sufficient.
 
L

LeonKh

Thank you for the reply! Clean exit of Outlook is such a struggle. It looks
like this exception starts happening together with a warning in the right
lower corner of Outlook window:
"A data file did not close properly the last time it was used and is being
checked for problems. performance might be affected while the check is in
progress."

Here the method I use, which has some debugging/troubleshooting parts:

public void DeleteAllEmailsInFolder(MAPIFolder mpf) {
Trace.TraceInformation("Deleting items in folder \"{0}\"",
mpf.FullFolderPath);

// Increments delay between successive deletions, in millisecs.
int timeStep = 100;
// Limits the maximum delay, in millisecs.
int timeOut = 10000;

// Logon.
NameSpace outlookNameSpace = myOutlookApp.GetNamespace("MAPI");

// Determine how many items in folder.
int totalNumberOfItemsInFolderToDelete = mpf.Items.Count;

if (totalNumberOfItemsInFolderToDelete > 0) {
int mCounter = 0; // deleted mail item counter
int rCounter = 0; // deleted report item counter
int aCounter = 0; // deleted appointment item counter

// Iterate thru all items and call the delete method on
emails.
for (int i = totalNumberOfItemsInFolderToDelete;
i >= 1; i--) {

Object Item = mpf.Items;

if (Information.TypeName(Item) ==
"MailItem") {
int beforeDelete = mpf.Items.Count;
MailItem outlookMailItem = (MailItem)Item;
outlookMailItem.Delete();
//@ A delay for processing deletion
int timer = 0;
while (mpf.Items.Count != (beforeDelete - 1)) {
Thread.Sleep(timeStep);
timer += timeStep;
if (timer >= timeOut) {
Trace.TraceError(
"mDeletion was not completed within " +
"{0} millisecs.", timeOut);
break;
}
}
if (timer < timeOut) {
Trace.TraceInformation(
"mDeletion completed within {0}
millisecs.",
timer);
}

mCounter++;
continue;
}

if (Information.TypeName(Item) ==
"ReportItem") {
int beforeDelete = mpf.Items.Count;
ReportItem outlookReportItem = (ReportItem)Item;
outlookReportItem.Delete();
//@ A delay for processing deletion
int timer = 0;
while (mpf.Items.Count != (beforeDelete - 1)) {
Thread.Sleep(timeStep);
timer += timeStep;
if (timer >= timeOut) {
Trace.TraceError(
"rDeletion was not completed within " +
"{0} millisecs.", timeOut);
break;
}
}
if (timer < timeOut) {
Trace.TraceInformation(
"rDeletion completed within {0}
millisecs.",
timer);
}

rCounter++;
continue;
}

if (Information.TypeName(Item) ==
"AppointmentItem") {
int beforeDelete = mpf.Items.Count;
AppointmentItem outlookAppointmentItem =
(AppointmentItem)Item;
outlookAppointmentItem.Delete();
//@ A delay for processing deletion
int timer = 0;
while (mpf.Items.Count != (beforeDelete - 1)) {
Thread.Sleep(timeStep);
timer += timeStep;
if (timer >= timeOut) {
Trace.TraceError(
"aDeletion was not completed within " +
"{0} millisecs.", timeOut);
break;
}
}
if (timer < timeOut) {
Trace.TraceInformation(
"aDeletion completed within {0}
millisecs.",
timer);
}

aCounter++;
}
}
int totalDeleted = mCounter + rCounter + aCounter;

Trace.TraceInformation("{0} items have been deleted in " +
"\"{1}\" folder.", totalDeleted, mpf.FullFolderPath);

} else {
Trace.TraceInformation("There are no items to " +
"delete in \"{0}\"", mpf.FullFolderPath);
}
}
 
K

Ken Slovak - [MVP - Outlook]

What Eric was blogging about is more a problem with Outlook 2003 and earlier
than with Outlook 2007.

It's known as the Catch-22 bug, Outlook won't close if you have objects in
scope but how to know to release those objects? The Outlook team did a lot
of work in Outlook 2007 to make sure OnDisconnection() always fires as a
signal to release the Outlook objects.

However, most of us do use Inspector and Explorer wrappers and check for the
Close() event on each, then check for any existing instances of those
objects. When the last Explorer or Inspector is closing that's the signal to
release everything. I've been using variations of that approach since the
Outlook 2000 beta, and my public templates from my Outlook 2007 book have
implementations of similar wrappers and checks for closing.

I also agree with Eric about the importance of shimming managed code shared
addins, and the possible hazards of calling the Marshal methods of releasing
COM objects. When you do that you release the RCW for that object and it's
global to that AppDomain.

For example, a MailItem object is instantiated in one class and passed to
another. The second class calls Marshal.ReleaseComObject() on the MailItem.
When the first class tries to do something then with that mail item you'll
get a COM exception since the RCW was released for the object even though
released on the copy in class 2.

So you have to call those methods with care, but there are times you do need
to do so. You just have to be aware of all references to those objects
globally in your code and where you need to maintain the references and
where you don't.

Some times in a loop people use a construct like this:

for (int i = 1; i < itemCount; i++)
{
Outlook.MailItem foo = groupSelection;
// do something
}

That will create a new object each pass through the for loop. In a case like
that where the foo object is local to the loop calling
Marshal.ReleaseComObject() will affect only that instance of the object.

Declaring foo outside of the loop just sets the object to a new value in
each loop pass. Calling Marshal.ReleaseComObject() in that case within the
loop will fire exceptions on the next loop pass since the RCW was released
for foo and it's never instantiated as a new object again.

So you really have to study and understand your code, and always use a
shimmed approach to maintain your own AppDomain.
 
K

Ken Slovak - [MVP - Outlook]

One of the problems I see are lots of usages that will create internal
objects that can't be explicitly released. For example something as simple
as this:

int totalNumberOfItemsInFolderToDelete = mpf.Items.Count;

That creates an internal object for mpf.Items. A better approach that gives
you more control over lifetime is to use this:

Outlook.Items myItems = mpf.Items;
int totalNumberOfItemsInFolderToDelete = myItems.Count;

That allows you to release myItems explicitly when the time comes. Same
thing for any other object that uses compound dot operators.

Within the loop something like this is another example:

Object Item = mpf.Items;

Each loop pass not only creates the internal object for mpf.Items, but it
also creates a new instance of Item. That could be declared outside the loop
as

Object Item = null;

If declared within the loop, without compound dot operators, that would be a
candidate for calling Marshal.ReleaseComObject(). Otherwise I'd just null
that object in each loop pass.

Go through the code with these things in mind and see if it makes a
difference.




LeonKh said:
Thank you for the reply! Clean exit of Outlook is such a struggle. It
looks
like this exception starts happening together with a warning in the right
lower corner of Outlook window:
"A data file did not close properly the last time it was used and is being
checked for problems. performance might be affected while the check is in
progress."

Here the method I use, which has some debugging/troubleshooting parts:

public void DeleteAllEmailsInFolder(MAPIFolder mpf) {
Trace.TraceInformation("Deleting items in folder \"{0}\"",
mpf.FullFolderPath);

// Increments delay between successive deletions, in millisecs.
int timeStep = 100;
// Limits the maximum delay, in millisecs.
int timeOut = 10000;

// Logon.
NameSpace outlookNameSpace = myOutlookApp.GetNamespace("MAPI");

// Determine how many items in folder.
int totalNumberOfItemsInFolderToDelete = mpf.Items.Count;

if (totalNumberOfItemsInFolderToDelete > 0) {
int mCounter = 0; // deleted mail item counter
int rCounter = 0; // deleted report item counter
int aCounter = 0; // deleted appointment item counter

// Iterate thru all items and call the delete method on
emails.
for (int i = totalNumberOfItemsInFolderToDelete;
i >= 1; i--) {

Object Item = mpf.Items;

if (Information.TypeName(Item) ==
"MailItem") {
int beforeDelete = mpf.Items.Count;
MailItem outlookMailItem = (MailItem)Item;
outlookMailItem.Delete();
//@ A delay for processing deletion
int timer = 0;
while (mpf.Items.Count != (beforeDelete - 1)) {
Thread.Sleep(timeStep);
timer += timeStep;
if (timer >= timeOut) {
Trace.TraceError(
"mDeletion was not completed within " +
"{0} millisecs.", timeOut);
break;
}
}
if (timer < timeOut) {
Trace.TraceInformation(
"mDeletion completed within {0}
millisecs.",
timer);
}

mCounter++;
continue;
}

if (Information.TypeName(Item) ==
"ReportItem") {
int beforeDelete = mpf.Items.Count;
ReportItem outlookReportItem = (ReportItem)Item;
outlookReportItem.Delete();
//@ A delay for processing deletion
int timer = 0;
while (mpf.Items.Count != (beforeDelete - 1)) {
Thread.Sleep(timeStep);
timer += timeStep;
if (timer >= timeOut) {
Trace.TraceError(
"rDeletion was not completed within " +
"{0} millisecs.", timeOut);
break;
}
}
if (timer < timeOut) {
Trace.TraceInformation(
"rDeletion completed within {0}
millisecs.",
timer);
}

rCounter++;
continue;
}

if (Information.TypeName(Item) ==
"AppointmentItem") {
int beforeDelete = mpf.Items.Count;
AppointmentItem outlookAppointmentItem =

(AppointmentItem)Item;
outlookAppointmentItem.Delete();
//@ A delay for processing deletion
int timer = 0;
while (mpf.Items.Count != (beforeDelete - 1)) {
Thread.Sleep(timeStep);
timer += timeStep;
if (timer >= timeOut) {
Trace.TraceError(
"aDeletion was not completed within " +
"{0} millisecs.", timeOut);
break;
}
}
if (timer < timeOut) {
Trace.TraceInformation(
"aDeletion completed within {0}
millisecs.",
timer);
}

aCounter++;
}
}
int totalDeleted = mCounter + rCounter + aCounter;

Trace.TraceInformation("{0} items have been deleted in " +
"\"{1}\" folder.", totalDeleted, mpf.FullFolderPath);

} else {
Trace.TraceInformation("There are no items to " +
"delete in \"{0}\"", mpf.FullFolderPath);
}
}
 
L

LeonKh

It looks like moving declarations out of the loop did help. So far the
automation completed about 10 runs without exceptions. Will let it go
overnight.
Thank you!
 
L

LeonKh

I see. Definitely will get your book. Maybe it would be possible for somebody
experienced like you to go through our code (less than 1000 lines, I would
say) and guide me while refactoring it? It would be a great way for me to
learn.
 
K

Ken Slovak - [MVP - Outlook]

I do consulting work like that, but here's not really the place to discuss
that. If you're interested in having me consult on your code ping me off
list.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top