Welcome to X4U
Minimize 

Hello visitor.

This is the personal homepage of Helmut Obertanner.
I'm a computer freak and electronic enthusiast and I live in Munich in Germany - the hometown of the famous Oktoberfest.
Why the name X4U? In my case it's a synonym for everything is possible.
I'm an experienced full time C# developer and doing so since .Net Framework 1.0.
As technical enthusiast I started Office development using .Net technologies.
As I searched answers and found help in the Internet I decided to share my knowledge in newsgroups and forums too. For participation, helping other developers, writing how-to's and articles and doing consulting and trainings I received the Microsoft MVP Award for the 3rd time.

As Outlook C# specialist I founded the website Outlooksharp.de
Beside software development, I'm building electronic devices for customers.
Prototypes with SMD PCBs and complete electronic devices with USB connections are my typical portfolio.
You got an Idea and have no plan how to put it to work - give me a call and I tell you if I can help.
A complete solution starts from defining the requirements, design the schematic and PCB, build a functional prototype, program the firmware, program the windows software, design and build a housing prototype.
Usually I work with very cheap PIC 18F Microcontrollers, these multifunctional devices are programmed in C language. 

I'm located in Germany with satisfied customers around the globe.
Enjoy the content of my site and feel free to give me your feedback.
You're welcome.
X4U Blog
 
Dec26

Written by:Helmut Obertanner
12/26/2009 5:47 PM 

In diesem wie-wird's-gemacht Artikel möchte ich verschiedene Überlegungen zum Thema Rekursiver Zugriff auf Outlook Ordner und deren Unterordner.

Im Outlook Objekt Model (OOM) wird ein Outlook Ordner durch den MAPIFolderTyp representiert .
Es gibt verschiedene Möglichkeiten auf eine MAPIFolder-Instanz zuzugreifen:

  • Verwendung der Session.GetDefaultFolder(OlDefaultFolders) Methode um Zugriff euf einen der vordefinierten Ordner zu erhalten
  • Mit der Session.GetFolderById(folderId, [optional] storeId) Methode wird ein Ordner anhand seiner EntryId geladen
  • Über die Parent Eigenschaft eines Item Objekts oder eines anderen Ordners
  • Über die Explorer.CurrentFolder Eigenschaft
  • Iterieren über die Folders ansammlung eines Stores oder einer Folder Instanz.
  • Durch erstellen eines neuen Folders

Das rekursive iterieren über einen Ordnerbaum und der Zugriff auf dessen Outlook Objekte kann ein ziemlich langsames unterfangen werden. Es hängt ab von der Anzahl der Items in einem Ordner und der ebenentiefe der Unterordner. Wenn das Outlook Object Model mit .Net(managed) code verwendet wird, muss man sich über die verwendung des Speichers und der COM Objekte Gedanken machen. Der Zugriff und die Verwendung von Objekten wie Emails, Termine(Appointments) and Aufgaben(Tasks) kann sehr langsam sein. Dies liegt leider am internen Design der MAPI Struktur, und wie die Daten gespeichert sind. Das Outlook Objekt Model basiert auf dieser Technologie. Ausserdem spielt die Konfiguration eine Rolle, wenn ein Exchangeserver verwendet wird ist es ein Unterschied ob man online oder offline arbeitet – und wie schnell eine Netzwerkverbindung ist.

Das zu viele RPC Verbindungen problem

Diese Problem kann auftreten, wenn man mit einem Exchangeserver im Backend arbeitet und auf viele Outlook-Objekte in kurzer Zeit zugreift. Für jedes Item auf welches man per Code zugreift, wird eine Verbindung zum Exchangeserver hergestellt (Remote Procedure Call) und das entsprechende Objekt wird geöffnet oder synchronisiert. Im Standardfall ist die Anzahl der gleichzeitig möglichen RPC-Verbindungen von einem Client(Outlook Session) zum Exchangeserver begrenzt auf 256. Wenn man eine Lösung entwickelt und nur offline Arbeitet oder zum Testen nur wenige Objekte verwendet wird dieses Problem nie auftreten. Wird die Anwendung jedoch beim Kunden in Betrieb genommen und verwendet entstehen plötzlich unerklärliche Phänomene. Also: Wann immer über eine Outlook-Items Ansammlung iterierd wird, sollte man diese Problem im Hinterkopf haben und diese schon im Voraus vermeiden. Im ersten Beispiel wird einfach über alle Items im Posteingang iteriert. Hier werden verschiedene Grundlagen besprochen. Für alle Beispiele wird folgende Methode verwendet, welche den Posteingangs-Ordner zurückliefert.

Outlook.MAPIFolder GetInboxFolder() {

returnGlobals.ThisAddIn.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);

}

 

Im ersten Test iterieren wir einfach über alle Email-Items des Posteingangs und lassen die Betreff8Subject)-Zeile ausgeben.

// Get the Inbox folder

Outlook.MAPIFolder inboxFolder = GetInboxFolder();

 

// Iterate over all Inbox items and get the Subject

foreach (object item in inboxFolder.Items) {

Trace.WriteLine (item.GetType ().InvokeMember ("Subject", BindingFlags.GetProperty, null, item, null));

}

 

Was ist an diesem Code falsch? Theoretisch ist eigentlich alles OK, jedoch:
- was passiert, wenn mehr Items als mögliche RPC-Connections im Ordner sind?
- die Anzahl der Objekte in der Items-Collection kann sich jederzeit dynamisch ändern – wie reagiert der Code darauf?
- der gesamte Vorgang kann nicht unterbrochen werden, bis alle Objekte verarbeitet wurden.
- der Programmcode läuft im Haput-Anwendungsthread des Outlook Prozesses ab – dies bedeutet: Die Benutzeroberfläche reagiert nicht mehr, bis die Schleife zu Ende ist.

Wie kann man das lösen?

Zuerst wollen wir uns darum kümmern das RPC-Verbindungsproblem zu lösen. Dazu definiert man eine Variable, welche die Anzahl der verarbeiteten Objekte mitzählt. Innerhalb der Anwendungsschleife erhöht man einfach einen Zählerund erreicht man eine vorbestimmte Anzahl von Objekten, erzwingt man den the .Net Garbage Collector die nicht mehr verwendeten COM-Objekte freizugeben und diese aus dem Speicher zu entfernen. Die Ursache des Problems ist: Der .Net Garbage Collector ist intelligent. Dieser merkt sich die verwendeten Objekte für eine gewisse Zeit in einem cache – falls man diese Objekte spatter wiederverwendet, erhält man einen schnellen Zugriff darauf – dadurch werden .Net Anwendungen schneller. Dieses Verhalten trifft leider auch auf COM(Component Object Model)-Objekte also alle Outlook-Objekte zu. Greift man auf ein solches Objekt zu, verbleibt für eine unbestimmte Zeit eine Referenz darauf im Speicher – auch wenn man das Objekt explizit auf NULL setzt. Der GC(Garbage Collector) entscheidet, wann diese Objekt aus dem Speicher freigegeben wird. Als Alternative kann man aber den Garbage Collector dazu zwingen den Speicher aufzuräumen und nicht mehr verwendete Objekte freizugeben. Achtung: Ein GC cleanup ist sehr teuer den er verbraucht CPU-Zeit und blockiert auch andere .Net Anwendungen – man sollte Ihn also möglichst nicht zu oft aufrufen – man muss eben einen guten Mix finden. Bei der Outlook-programmierung ist es ein absolutes muss, einen GC-Cleanup nach der freigabe eines Inspector oder eines Explorer Objekts durchzuführen.
Hier kommt das nächste Code-Beispiel.

// Get the Inbox folder

Outlook.MAPIFolder inboxFolder = GetInboxFolder();

 

int watchDog = 0;

 

// Iterate over all Inbox items and get the Subject

foreach (object item in inboxFolder.Items) {

Trace.WriteLine (item.GetType ().InvokeMember ("Subject", BindingFlags.GetProperty, null, item, null));

 

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

Die watchdog Variable wird benutzt, um ein Cleanup des the Garbage Collectors nach einer bestimmten Anzahl Verwendungen von COM Objekten zu erzwingen – dabei warden die RPC-Verbindungen geschlossen.
Anmerkung: Die Folder.Items Ansammlung ist nicht statisch – die Anzahl der Objekte kann sich jederzeit ändern. Anstatt einer foreach-Schleiche sollte man deshalb besser die Anzahl der Objekte ermitteln und einen Rückwärtszähler programmieren.
So sieht der Code dafür aus:

// Get the Inbox folder

Outlook.MAPIFolder inboxFolder = GetInboxFolder();

 

int watchDog = 0;

// Iterate over all Inbox items and get the Subject

for (int index = inboxFolder.Items.Count; index > 0; index-- ) {

object item = inboxFolder.Items[index];

Trace.WriteLine(item.GetType().InvokeMember("Subject", BindingFlags.GetProperty, null, item, null));

 

item = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

Die index Variable um die Objekte rückwärts herunter abzuarbeiten. So können während des Vorgangs Objekte in der Schleife gelöscht oder verschoben werden. Der gesamte Vorgang kann aber immer noch nicht unterbrochen warden, nicht mal durch Beenden der Outlook Anwendung. Um den Code in einen unterbrechbaren und interaktiven Vorgang zu ändern, muss dieser in einem eigenen Thread laufen. Im .Net Framework gibt es dafür schon etwas was man hier benutzen kann, es nennt sich BackgroundWorker.
Nachfolgend kann man den Beispielcode dafür sehen:

BackgroundWorker _worker;

 

privatevoid ThisAddIn_Startup(object sender, System.EventArgs e)

{

// Setup the background thread

_worker = newBackgroundWorker();

_worker.DoWork += newDoWorkEventHandler(_worker_DoWork);

_worker.WorkerSupportsCancellation = true;

_worker.RunWorkerAsync();

}

 

void _worker_DoWork(object sender, DoWorkEventArgs e) {

BackgroundWorker worker = (BackgroundWorker)sender;

 

// Get the Inbox folder

Outlook.MAPIFolder inboxFolder = GetInboxFolder();

 

int watchDog = 0;

// Iterate over all Inbox items and get the Subject

for (int index = inboxFolder.Items.Count; index > 0; index--) {

 

// if someone interrupts the worker exit here

if (worker.CancellationPending) break;

 

object item = inboxFolder.Items[index];

Trace.WriteLine(item.GetType().InvokeMember("Subject", BindingFlags.GetProperty, null, item, null));

 

item = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

}

 

privatevoid ThisAddIn_Shutdown(object sender, System.EventArgs e)

{

if (_worker.IsBusy) {

_worker.CancelAsync();

}

}

 

Mit einem BackgroundWorker last sich ein Zeitraubender Prozess im Hintergrund durchführen. Die Anwendungsoberfläche bleibt benutzbar und die Anwendungen bedienbar. Ausserdem kann die Verarbeitung unterbrochen werden.

Rekursive Operationen

Als nächstes werden wir in der Schleife rekursiv über Ordner iterieren. Dazu werden wir eine Methode implementieren, welche sich selbst rekursiv aufruft.

void IterateFolder(Outlook.MAPIFolder folder) {

int watchDog = 0;

// Iterate over all Inbox items and get the Subject

for (int index = folder.Items.Count; index > 0; index--) {

 

object item = folder.Items[index];

Trace.WriteLine(item.GetType().InvokeMember("Subject", BindingFlags.GetProperty, null, item, null));

 

item = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

 

for (int index = folder.Folders.Count; index > 0; index--) {

 

Outlook.MAPIFolder subFolder = folder.Folders[index];

 

// Call this function recursively

IterateFolder(subFolder);

 

subFolder = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

}

 

Dies funktioniert zwar tatsächlich, es ist aber leider nicht die beste Lösung des Problems. Rekursives aufrufen von Funktionen kann den Stapelspeicher des Prozessors überfordern – man landet im schlimmsten Fall im Nirwana. Wie kann man das lösen?
Als einfach Abhilfe kann man dazu das Queue Objekt verwenden, welches bereits durch das .Net Framework zur Verfügung gestellt wird. Eine Queue(Warteschlange) hat im wesentlichen 2 wichtige Methoden: Enqueue() und Dequeue().
Enqueue fügt ein Objekt am Ende der Schlange hinzu, Dequeue mimmt das erste Objket am Anfang der Queue und entfernt es aus dieser. Im nächsten Beispiel sammeln wir einfach die EntryID(Eindeutige ID des Ordners) der Unterordner eines Ordners und fügen diese am Ender der Schlange hinzu. In der Arbeitsmethode des Hintergrundthreads, wird einfach geprüft ob noch EntryID's in der Warteschlange sind, dann wird diese verarbeitet. Die EntryID eines Ordners wird aus der Queue entnommen und der Ordner wird verarbeitet – die ID's der Unterordner werden wieder ans Ende der Queue angefügt. Auf diesem Wege warden alle Ordner verarbeitet, und es gibt kein Problem mit dem Prozessor-Stack(Stapelspeicher). Der Prozess kann auch jederzeit unterbrochen werden.

BackgroundWorker _worker;

Queue<string> _folderIdsToProcess;

 

privatevoid ThisAddIn_Startup(object sender, System.EventArgs e) {

_folderIdsToProcess = newQueue<string>();

 

// Get the Inbox folder

Outlook.MAPIFolder inboxFolder = GetInboxFolder();

// and add it to the queue

_folderIdsToProcess.Enqueue(inboxFolder.EntryID);

 

// Setup the background thread

_worker = newBackgroundWorker();

_worker.DoWork += newDoWorkEventHandler(_worker_DoWork);

_worker.WorkerSupportsCancellation = true;

_worker.RunWorkerAsync();

}

 

void _worker_DoWork(object sender, DoWorkEventArgs e) {

 

BackgroundWorker worker = (BackgroundWorker)sender;

Outlook.MAPIFolder folder;

 

int watchDog = 0;

while (_folderIdsToProcess.Count > 0 && !worker.CancellationPending) {

 

// pick one folder

string entryId = _folderIdsToProcess.Dequeue();

folder = Globals.ThisAddIn.Application.Session.GetFolderFromID(entryId, Type.Missing);

 

// Iterate over all Inbox items and get the Subject

for (int index = folder.Items.Count; index > 0; index--) {

 

if (worker.CancellationPending) break;

 

object item = folder.Items [index];

Trace.WriteLine(item.GetType().InvokeMember("Subject", BindingFlags.GetProperty, null, item, null));

 

item = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

 

for (int index = folder.Folders.Count; index > 0; index--) {

 

if (worker.CancellationPending) break;

 

Outlook.MAPIFolder subFolder = folder.Folders[index];

 

// Call this function recursively

_folderIdsToProcess.Enqueue(subFolder.EntryID);

 

subFolder = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

 

folder = null;

}

}

 

privatevoid ThisAddIn_Shutdown(object sender, System.EventArgs e) {

if (_worker.IsBusy) {

_worker.CancelAsync();

}

}

 

Beim Zugriff auf die Queue-Variable gibt es auch keine locking-Probleme, da diese nur durch den Arbeitsthread(worker) modifiziert wird.

Beschleunigen des Zugriffs

Es ist Ihnen eventuell aufgefallen, dass der zugriff auf die Outlook-Objekte eine enorme Zeit beansprucht. Es gibt ein paar wenige Möglichkeiten diese Zeiten zu beschleunigen. Als erste Option würde ich die Verwendung eines Filters vorstellen: Ein Filter schränkt die Anzahl der Treffer in einem Ordner ein. Als Beispiel mächten wir nur Outlook-Items verarbeiten, welche nach einem speziellen Datum modifiziert wurden.

BackgroundWorker _worker;

Queue<string> _folderIdsToProcess;

 

privatevoid ThisAddIn_Startup(object sender, System.EventArgs e) {

_folderIdsToProcess = newQueue<string>();

 

// Get the Inbox folder

Outlook.MAPIFolder inboxFolder = GetInboxFolder();

// and add it to the queue

_folderIdsToProcess.Enqueue(inboxFolder.EntryID);

 

// build a filter

// note that this filter is build a little bit complicated

// This works even if you are using an Outlook even with another language format, not only en-us

DateTime from = DateTime.Now.AddMonths(-1);

DateTime to = DateTime.Now;

string filter = string.Format("[LastModificationTime] > '{0}/{1}/{2}' AND [LastModificationTime] <='{3}/{4}/{5}'"

, from.Year, from.Month, from.Day

, to.Year, to.Month, to.Day);

 

// Setup the background thread

_worker = newBackgroundWorker();

_worker.DoWork += newDoWorkEventHandler(_worker_DoWork);

_worker.WorkerSupportsCancellation = true;

_worker.RunWorkerAsync(filter);

}

 

void _worker_DoWork(object sender, DoWorkEventArgs e) {

 

BackgroundWorker worker = (BackgroundWorker)sender;

string filter = (string)e.Argument;

 

Outlook.MAPIFolder folder;

 

int watchDog = 0;

while (_folderIdsToProcess.Count > 0 && !worker.CancellationPending) {

 

// pick one folder

string entryId = _folderIdsToProcess.Dequeue();

folder = Globals.ThisAddIn.Application.Session.GetFolderFromID(entryId, Type.Missing);

Outlook.Items items = folder.Items.Restrict(filter);

 

// Iterate over all Inbox items and get the Subject

for (int index = items.Count; index > 0; index--) {

 

if (worker.CancellationPending) break;

 

object item = items[index];

Trace.WriteLine(item.GetType().InvokeMember("Subject", BindingFlags.GetProperty, null, item, null));

 

item = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

 

for (int index = folder.Folders.Count; index > 0; index--) {

 

if (worker.CancellationPending) break;

 

Outlook.MAPIFolder subFolder = folder.Folders[index];

 

// Call this function recursively

_folderIdsToProcess.Enqueue(subFolder.EntryID);

 

subFolder = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

 

items = null;

folder = null;

}

}

 

privatevoid ThisAddIn_Shutdown(object sender, System.EventArgs e) {

if (_worker.IsBusy) {

_worker.CancelAsync();

}

}

 

Leider lassen sich zum Filtern nicht alle Eigenschaften eines Objekts verwenden.

Das Table Objekt

Seit Outlook 2007 gibt es ein neues Table Objekt im OOM welches schnellen Zugriff auf die Tabellendaten eines Ordners verspricht. Der Zugriff ist schnell, den man kann entscheiden welche Spalten überhaupt benötigt werden und muss nicht jedes mal ein komplettes Objekt durchladen. Die beste Performanz erhält man natürlich, wenn man nur die Daten in die Tabelle lädt, welche man zum Verarbeiten der Daten benötigt. Leider gilt auch hier, dass nicht sämtliche Daten direkt in der Tabelle bereitgestellt werden können. In unserem code verwenden wir ja nur die Eigenschaft Letztes Änderungsdatum(LastModificationTime) und den Betreff(Subject). Prüfen wir als mal wie schnell wir die Daten lesen können. Um den Vorgang zu messen, wird der Anwendungscode noch etwas modifiziert – es wird die Anzahl der Objekte gezählt, als auch die Verarbeitungszeit. Bei Verwendung von Outlook Objekten erhalten wir im Durschschnitt folgendes Ergebnis:

Processing 773 items took 11262ms

 

Nach Umbau des Codes mit Verwendung des Table-Objektes, liefert der Test auf meinem System aber folgende Werte:

Processing 773 items took 15162ms

 

Interessant. In all meinen Tests unter Verwendung von Outlook2010 (Beta2) zeigte der Test, dass die Verwendung des Folder.Table -Objektes langsamer war, als direkt über die Items selbst eine schleife laufen zu lassen. Theoretisch sollte die Verwendung von GetTable() wesentlich schneller sein, als alle Outlook Objekte durchzuladen. Dem ist aber nicht so – vielleicht liegt es aber auch an der Beta-Version von Outlook 2010.

Hier ist das Beispiel für die Verwendung des Table-Objektes:

BackgroundWorker _worker;

Queue<string> _folderIdsToProcess;

 

privatevoid ThisAddIn_Startup(object sender, System.EventArgs e)

{

_folderIdsToProcess = newQueue<string>();

 

// Get the Inbox folder

Outlook.MAPIFolder inboxFolder = GetInboxFolder();

 

// and add it to the queue

_folderIdsToProcess.Enqueue(inboxFolder.EntryID);

 

// build a filter

DateTime from = DateTime.Now.AddMonths(-6);

DateTime to = DateTime.Now;

string filter = string.Format("[LastModificationTime] > '{0}/{1}/{2}' AND [LastModificationTime] <='{3}/{4}/{5}'"

, from.Year, from.Month, from.Day

, to.Year, to.Month, to.Day);

 

// Setup the background thread

_worker = newBackgroundWorker();

_worker.DoWork += newDoWorkEventHandler(_worker_DoWork);

_worker.WorkerSupportsCancellation = true;

_worker.RunWorkerAsync(filter);

}

 

void _worker_DoWork(object sender, DoWorkEventArgs e) {

 

BackgroundWorker worker = (BackgroundWorker)sender;

string filter = (string)e.Argument;

 

Outlook.MAPIFolder folder;

 

int itemsProcessed = 0;

Stopwatch watch = newStopwatch();

watch.Start();

 

int watchDog = 0;

while (_folderIdsToProcess.Count > 0 && !worker.CancellationPending) {

 

// pick one folder

string entryId = _folderIdsToProcess.Dequeue();

folder = Globals.ThisAddIn.Application.Session.GetFolderFromID(entryId, Type.Missing);

Outlook.Table table = folder.GetTable(filter, Outlook.OlTableContents.olUserItems);

 

// try to get the first row

Outlook.Row row = table.GetNextRow ();

 

// Iterate over all Inbox items and get the Subject

while (!table.EndOfTable ) {

 

if (worker.CancellationPending) break;

 

string subject = (string) row["Subject"];

 

Trace.WriteLine(subject);

 

itemsProcessed++;

row = table.GetNextRow();

 

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

 

for (int index = folder.Folders.Count; index > 0; index--) {

 

if (worker.CancellationPending) break;

 

Outlook.MAPIFolder subFolder = folder.Folders[index];

 

// Call this function recursively

_folderIdsToProcess.Enqueue(subFolder.EntryID);

 

subFolder = null;

// Ensures that not too many RPC connections are made to a Server

watchDog++;

if (watchDog > 100) {

watchDog = 0;

GC.Collect();

GC.WaitForPendingFinalizers();

}

}

 

table = null;

folder = null;

}

 

Trace.WriteLine(string.Format("Processing {0} items took {1}ms", itemsProcessed, watch.ElapsedMilliseconds));

}

 

privatevoid ThisAddIn_Shutdown(object sender, System.EventArgs e)

{

if (_worker.IsBusy) {

_worker.CancelAsync();

}

}

 

Eine weitere Verbesserung könnte die Verwendung eines weiteren Threads sein. Ein Thread zum Auswerten, ob Items überhaupt verarbeitet werden müssen, ein weiterer Thread zum Verarbeiten der Items. Die Komplexität wird dadurch aber wesentlich erhöht, deshalb möchte ich hier darauf verzichten.

Vermeidung von GC.Collect()

Obwohl es nötig ist GC.Collect() bei der Programmierung von Outlook-Anwendungen ab und zu an verschiedenen Stellen aufzurufen, sollte dies nur sehr selten und nur wenn es unbedingt nötig ist geschehen. Ein Erzwingen von GC.Collect() beeinflusst die Laufzeit aller .Net Anwendungen. Innerhalb einer Schleife verwenden wir GC.Collect() nur deshalb um ein spezifisches Problem in Verbindung mit einem Exchangeserver zu umgehen. Wenn Outlook gar keinen Exchangeserver verwendet – oder offline d.h. nicht Verbunden ist, wird GC.Collect() nicht benötigt. Deshalb könnte man hier noch etwas optimieren.

Unten steht der optimierte Code:

privatevoid IncreaseWatchdogAndCallGCIfOnline(refint watchDog) {

watchDog++;

if (watchDog > 100) {

Outlook.OlExchangeConnectionMode mode = Globals.ThisAddIn.Application.Session.ExchangeConnectionMode;

if (mode == Outlook.OlExchangeConnectionMode.olCachedConnectedDrizzle

|| mode == Outlook.OlExchangeConnectionMode.olCachedConnectedFull

|| mode == Outlook.OlExchangeConnectionMode.olCachedConnectedHeaders

|| mode == Outlook.OlExchangeConnectionMode.olOnline) {

// Ensures that not too many RPC connections are made to a Server

 

 

GC.Collect();

GC.WaitForPendingFinalizers();

}

watchDog = 0;

}

}

 

GC.Collect() wird nur aufgerufen, wenn Outlook online mit einem Exchangeserver verbunden ist.

References

Outlook Folder Object: http://msdn.microsoft.com/en-us/library/bb176362.aspx
Outlook Table Object: http://msdn.microsoft.com/en-us/library/bb176406.aspx
.Net Queue Object: http://msdn.microsoft.com/en-us/library/7977ey2c.aspx

Tags:

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
Enter the code shown above in the box below
Add Comment  Cancel 
X4U Blog
 
Home  |  Hardware  |  Software  |  Services  |  Resources  |  Impressum