none
backgroundworker in outlook? RRS feed

  • Question

  •  

    Hi,

    I have an outlook addin 2007.

    Sometimes Outlook UI get stuck for a few seconds and I was wondering if it is possible to work with a backgroundworker?

    I read that it is problematic, can someone please elaborate on the subject about how to use

    backgroundworker correctly in outlook?

    My application involves catching  new appointment and mails items working with local DB

    I do this in the add item event

    Public Sub AddNewAppointment(ByVal Item As Object) Handles CalendarItemEvent.ItemAdd

    and in the change item event

    Public Sub ChangedAppointment(ByVal Item As Object) Handles CalendarItemEvent.ItemChange 

    Is it possible to work with backgroundworker in my application?

    Tanks,

    Patrick



    • Edited by Patrick12_3 Tuesday, March 20, 2012 6:53 PM
    Tuesday, March 20, 2012 6:46 PM

Answers

  • ItemChange event is a 'data' type event - it will fire whenever outlook decides to store changes made in UI, code, etc. to its local cache, store or exchange. It is up to outlook to decide when and how often it will fire, so yes, you could solve this problem by keepeing cache of values you care for per entry id and if event fires again, you check against your cache if values importnant for you changed, if not - return from ent handler doing nothing.

    As for DBUtils.getString method it should be like this (i completly ignore issue with iterating every single appointment and spanning thread/operating it in isolation, it is up to you to decide if you want to implement some batching, like operating on 10 items at once, etc.):

    store somehwere outlook;s Application or Session object and change

    foreach(object item in Domainsdf)

    {

    AppointmentItem ai = item as AppointmentItem;

    if(ai == null) continue;

    AppointmentContainer ac = new AppointmentContainer (ai);//stores EntryId, location, etc.

    Thread t = new Thread(_ =>

    {

    var results = DBUtils.getString(string.Format("your sql, whatever", ac.Location); 

    if(!result.ChangeNeeded)

    return;

    YourGUiMarshallForm.BeginInvoke(_ =>

    {

    //now we are back in main UI thread, we can access OOM

    Session.GetItemByItemId(ac.EntryId).Location = result.Location;

    })

    });

    }

    Everything we wrote above also should be considered (item can no longer exist, be already changed, etc.) so you must code defensivly.

    • Marked as answer by Patrick12_3 Thursday, March 22, 2012 12:08 PM
    • Unmarked as answer by Patrick12_3 Thursday, March 22, 2012 12:29 PM
    • Marked as answer by Patrick12_3 Thursday, March 22, 2012 1:42 PM
    Thursday, March 22, 2012 8:48 AM

All replies

  • You should not be using OOM in a secondary thread. there are no exceptions.

    What is your code that takes a long time to execute?


    Dmitry Streblechenko (MVP)
    http://www.dimastr.com/redemption
    Redemption - what the Outlook
    Object Model should have been
    Version 5.2 is now available!

    Tuesday, March 20, 2012 7:43 PM
  •  

    the application have a DB with 2 milion rows,  every time a user receive appointment or mail i am serching this DB.

    1. the user have a lot of appointments and mails, this makes the application slow
    2. for each appointment or mail item i am serching the DB..

    what about the SQL statements involve searching the DB can i use backgroundworker on them?

    What about the calendar folder? I am searching appointment there..

    Will it be helpful?

    Patrick

    Wednesday, March 21, 2012 8:25 AM
  • if you plan to use only OOM then you have to make sure your code spends in main UI thread as little as possible, for example, in event handler grab needed infor from outlook item, store it in some form of container/DTO and pass it to background thread to process and communicate with DB.
    Wednesday, March 21, 2012 8:35 AM
  •  

    Hi,

    and what about me creating of new appointments or mails? Or changing the appointments?

    I am entering the outlook from different thread from the backgroundworker

    I understood it can crash outlook no?

     can you please post an example?

    This is what i do:

    1. new appointment arrives
    2. searching in DB to get criteria.

    if creteria=true then

    'function that make new appointment or change existing one

    makenewappointment()

    else

    'deleting it

    DeleteCurrentappointment()

    End if

    Tanks,

    Patrick


    • Edited by Patrick12_3 Wednesday, March 21, 2012 11:03 AM
    Wednesday, March 21, 2012 11:03 AM
  • no touching of anything from Microsoft.Office.Interop.* namespace from background thread.

    In add-in startup event instantiate WindowsFormsSynchronizationContext and store it in some static field or somehwere else for access.

    If your logic does not require user interaction or is not a result of clicking a button then on main UI thread subscribe to events and in event handler grab entryid of that element and needed info to make decision and send that info to background thread. Be aware that you cannot pass any Office objects there, strings, ints, list<T> are ok, but no objects from namespace as mentioned above.

    In background thread connect to your DB, do what you need and if you decide that new appointment must be created or change to existing one, call Post on synchronization context you stored before with all needed data and entryid of appointment. Delegate that you pass to Post method will be executed on main UI thread, so there call Session.GetItemByItemId and pass your entryid. Get appointment, make your changes, call save.

    Wednesday, March 21, 2012 11:59 AM
  • An alternative to using a SynchronizationContext, which does work well by calling System.Threading.SendOrPostCallback, is to set up a form in the main thread and have procedures you can call in the form. You can then use the form's InvokeRequired() method to see if you need to invoke the method to call it on the main thread.
     
    At times it can be difficult to get a SynchronizationContext on the main thread, depending on when you try it and when the Windows message pump is primed.

    --
    Ken Slovak
    MVP - Outlook
    http://www.slovaktech.com
    Author: Professional Programming Outlook 2007
     
     
    "DamianD" <=?utf-8?B?RGFtaWFuRA==?=> wrote in message news:47e0a960-7a36-439f-844f-ff1e59962319...

    no touching of anything from Microsoft.Office.Interop.* namespace from background thread.

    In add-in startup event instantiate WindowsFormsSynchronizationContext and store it in some static field or somehwere else for access.

    If your logic does not require user interaction or is not a result of clicking a button then on main UI thread subscribe to events and in event handler grab entryid of that element and needed info to make decision and send that info to background thread. Be aware that you cannot pass any Office objects there, strings, ints, list<T> are ok, but no objects from namespace as mentioned above.

    In background thread connect to your DB, do what you need and if you decide that new appointment must be created or change to existing one, call Post on synchronization context you stored before with all needed data and entryid of appointment. Delegate that you pass to Post method will be executed on main UI thread, so there call Session.GetItemByItemId and pass your entryid. Get appointment, make your changes, call save.


    Ken Slovak MVP - Outlook
    Wednesday, March 21, 2012 1:36 PM
    Moderator
  •  

    I understand you want to keep the form hidden and InvokeRequired to return to main thread of outlook..

    inside the form connect to DB.. but how to return to outlook main thread  if i am in the form thread ?

    can someone show me a snippet of how to do that? (Creating and changing appointments using InvokeRequired )

     Tanks

    Patrick

    Wednesday, March 21, 2012 1:50 PM
  • going with Ken's suggestion about form - form thread = main UI thread (outlook). If you want to communicate with your DB, span other thread or use ThreadPool.QueueUserWorkItem.

    when you are ready to go back to main thread, call inside form something like this:

    this.BeginInvoke (

    () =>

    {

    var appointment = (AppointmentItem)Application.Session.GetItemByItemId(entryId);

    //do your work

    });

    Wednesday, March 21, 2012 2:10 PM
  • The idea would be something like this. In the OnConnection() or Startup() methods, when you're guaranteed to be in the main Outlook addin thread, you instantiate a class level form:
     
    internal System.Windows.Form _threadMarshal = null;
     
    // in OnConnection() or Startup()
     
    _threadMarshal = new System.Windows.Form();
     
    Then you have methods to call in that form. The general model for InvokeRequired is detailed in http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
     
    I usually put something like this in my form and call it before I use any method in the form:
     

    internal bool InvokeRequired

    {

        get

        {

        try

        {

            bool res = this._form.InvokeRequired;

            return res;

        }

        catch ()

        {

            throw;

        }

        }

    }

    --
    Ken Slovak
    MVP - Outlook
    http://www.slovaktech.com
    Author: Professional Programming Outlook 2007
     
     
    "Patrick12_3" <=?utf-8?B?UGF0cmljazEyXzM=?=> wrote in message news:f6bc23a5-907d-43d7-896f-84592040ee1b...
     

    I understand you want to keep the form hidden and InvokeRequired to return to main thread of outlook..

    inside the form connect to DB.. but how to return to outlook main thread  if i am in the form thread ?

    can someone show me a snippet of how to do that? (Creating and changing appointments using InvokeRequired )

     Tanks

    Patrick


    Ken Slovak MVP - Outlook
    Wednesday, March 21, 2012 2:14 PM
    Moderator
  •  

    Tanks a lot Ken and Damian I will try using your advice can I keep this post open in the meantime?

     

    Patrick

    Wednesday, March 21, 2012 2:38 PM
  • Hi,

    I checked to see if it helps it seems that it is slower now

    Can you please look at the code that I wrote it correctly?

    add a winform _threadMarshal  to addin

    the addin

    Imports System.Threading
    Public Class ThisAddIn
        Dim CalendarComanderItem As New EventCalendarHendler
        Public Shared WithEvents threadMarshal As _threadMarshal = Nothing
        Private Sub ThisAddIn_Startup() Handles Me.Startup
            threadMarshal = New _threadMarshal
            CalendarComanderItem.InitialTheHendlerEvents(Me.Application)
        End Sub
        Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown
        End Sub
    End Class
    Public Class EventCalendarHendler
        Dim WithEvents CalendarItemEvent As Outlook.Items
        Public Sub InitialTheHendlerEvents(ByVal Application As Outlook.Application)
            Try
                Dim outlookNamespace As Outlook.NameSpace = Application.GetNamespace("MAPI")
                Dim calendar As Outlook.MAPIFolder = outlookNamespace.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar)
                CalendarItemEvent = calendar.Items
            Catch ex As Exception
            End Try
        End Sub
        Public Sub ChangedAppointment(ByVal Item As Object) Handles CalendarItemEvent.ItemChange
            Try
                ThisAddIn.threadMarshal.demoThread = New Thread( _
                New ParameterizedThreadStart(AddressOf ThisAddIn.threadMarshal.setapp))
                ThisAddIn.threadMarshal.demoThread.Start(Item)
            Catch ex As Exception
                MsgBox(ex.ToString)
            End Try
        End Sub
        Public Shared Sub CompareAndChangeAppointment(ByVal Location As String)
            Try
                ' iterate all appointments with the same location and change and save them..and read from DB
            Catch ex As Exception
                MsgBox(ex.ToString)
            End Try
        End Sub
    End Class

    the form

    Imports System.Threading
    Public Class _threadMarshal
        Public Shared appointmentId As String
        Delegate Sub CallAppointmentAgain(ByVal Item As Object)
        Public demoThread As Thread = Nothing
        Public ReadOnly Property GetInvokeRequired() As Boolean
            Get
                Return Me.InvokeRequired
            End Get
        End Property
        Public Sub setapp(ByVal Item As Object)
            changeAppointmentthread(Item)
        End Sub
        Public Sub changeAppointmentthread(ByVal Item As Object)
            Try
                If Me.GetInvokeRequired Then
                    Dim d As New ContextCallback(AddressOf setapp)
                    Me.Invoke(d, New Object() {Item})
                Else
                    EventCalendarHendler.CompareAndChangeAppointment(Item.Location)
                End If
            Catch ex As Exception
                MsgBox(ex.ToString)
            End Try
        End Sub
    End Class

    Wednesday, March 21, 2012 6:51 PM
  • you pass outlook's item to background thread - i thought we were clear that you cannot do this - gather needed info from item on main thread, stuff it into some class or struct and pass that into background thread. In that thread do what you want and only if you decide it is time to modify outlook items again, call Invoke or BeginInvoke (depending if you need some feedback from calling back to main UI or not) to grab that outlook item again by its entryid and modify it.

    so your example should be changed:

    in ChangedAppointment method cast Item to AppointmentItem, grab its entryid and all other data that your logic needs, stuff it into custom AppointmentItemContainer class (create it) and pass that container to Thread class.

    in setapp function (executed on backgriund thread) call your DB, check what you need and only after you finish at the end of that method use following call: Me.BeginInvoke(changeAppointment) passing back container class again (stuffed with additional data from db, etc.)

    in changeAppointment method use following code

    Application.Session.GetItemByItemId(container.EntryId)

    so you will get your original appointment back (be prepared that it might no longer exists or could be changed in the meantime) and modify it according to your logic. 

    Wednesday, March 21, 2012 8:51 PM
  • In addition to what Damian said, I don't understand why you're calling different methods depending on whether or not invocation's required. Wouldn't you either call the method directly if no invocation is required, and call it via invocation if it is required?

    --
    Ken Slovak
    MVP - Outlook
    http://www.slovaktech.com
    Author: Professional Programming Outlook 2007
     
     
    "Patrick12_3" <=?utf-8?B?UGF0cmljazEyXzM=?=> wrote in message news:25a4c6ec-07ad-42be-8e7c-7038ff010492...

    Hi,

    I checked to see if it helps it seems that it is slower now

    Can you please look at the code that I wrote it correctly?

    add a winform _threadMarshal  to addin

    the addin

    Imports System.Threading
    Public Class ThisAddIn
        Dim CalendarComanderItem As New EventCalendarHendler
        Public Shared WithEvents threadMarshal As _threadMarshal = Nothing
        Private Sub ThisAddIn_Startup() Handles Me.Startup
            threadMarshal = New _threadMarshal
            CalendarComanderItem.InitialTheHendlerEvents(Me.Application)
        End Sub
        Private Sub ThisAddIn_Shutdown() Handles Me.Shutdown
        End Sub
    End Class
    Public Class EventCalendarHendler
        Dim WithEvents CalendarItemEvent As Outlook.Items
        Public Sub InitialTheHendlerEvents(ByVal Application As Outlook.Application)
            Try
                Dim outlookNamespace As Outlook.NameSpace = Application.GetNamespace("MAPI")
                Dim calendar As Outlook.MAPIFolder = outlookNamespace.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar)
                CalendarItemEvent = calendar.Items
            Catch ex As Exception
            End Try
        End Sub
        Public Sub ChangedAppointment(ByVal Item As Object) Handles CalendarItemEvent.ItemChange
            Try
                ThisAddIn.threadMarshal.demoThread = New Thread( _
                New ParameterizedThreadStart(AddressOf ThisAddIn.threadMarshal.setapp))
                ThisAddIn.threadMarshal.demoThread.Start(Item)
            Catch ex As Exception
                MsgBox(ex.ToString)
            End Try
        End Sub
        Public Shared Sub CompareAndChangeAppointment(ByVal Location As String)
            Try
                ' iterate all appointments with the same location and change and save them..and read from DB
            Catch ex As Exception
                MsgBox(ex.ToString)
            End Try
        End Sub
    End Class

    the form

    Imports System.Threading
    Public Class _threadMarshal
        Public Shared appointmentId As String
        Delegate Sub CallAppointmentAgain(ByVal Item As Object)
        Public demoThread As Thread = Nothing
        Public ReadOnly Property GetInvokeRequired() As Boolean
            Get
                Return Me.InvokeRequired
            End Get
        End Property
        Public Sub setapp(ByVal Item As Object)
            changeAppointmentthread(Item)
        End Sub
        Public Sub changeAppointmentthread(ByVal Item As Object)
            Try
                If Me.GetInvokeRequired Then
                    Dim d As New ContextCallback(AddressOf setapp)
                    Me.Invoke(d, New Object() {Item})
                Else
                    EventCalendarHendler.CompareAndChangeAppointment(Item.Location)
                End If
            Catch ex As Exception
                MsgBox(ex.ToString)
            End Try
        End Sub
    End Class


    Ken Slovak MVP - Outlook
    Wednesday, March 21, 2012 9:56 PM
    Moderator
  • Hi, i am quait new in threading ....

    this is an example can you please look at it.

      Public Sub Foo()
            Dim outlookNamespaceas As Outlook.NameSpace = Globals.ThisAddIn.Application.Session
            Dim calendardf As Outlook.MAPIFolder = outlookNamespaceas.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar)
            Dim Domainsdf As Outlook.Items = calendardf.Items
            Domainsdf.Sort("[Start]", Descending:=False)
            Dim newmsg As String = ""
            Dim obj As Object
            For Each obj In Domainsdf
                If TypeOf obj Is Outlook.AppointmentItem Then
                    'get new message from DB
                    newmsg = DBUtils.getString(String.Format("select Message from appointmentDt where (location = '{0}') ", StrConv(obj.Location, VbStrConv.Lowercase)))
                    obj.Message = newmsg
                    obj.save()
                End If
            Next
        End Sub

     

    DBUtils.getString search local DB with 2 million rows this what takes a lot of time and the fact that I search all appointments

    can you please show me how to send DBUtils.getString to a different thread and return to main outlook thread with the parameters required? I don’t know how to invoke a function from the form to main outlook Thread… and be synchronized?

    Thursday, March 22, 2012 7:16 AM

  • i have another Q
    i am encountering something very strange in the changeappointment function Items keep getting invoked
    but not from my application the appointment item isn’t getting changed at all
    the code below show me msgbox even if i do nothing... i want to avoid that

     Public Sub ChangedAppointment(ByVal Item As Object) Handles CalendarItemEvent.ItemChange
            Try
                If TypeOf Item Is Outlook.AppointmentItem Then
                    MsgBox("Item change event")
                End If
            Catch ex As Exception
                MsgBox(ex.ToString)
            End Try
        End Sub 

    I though about Damian suggestion maybe keeping a class with all appointments EntryID and other fields and before searching the application search the class inner List for the EntryID and compare new Parameters to old ones if indeed it is different then continue with the function implementation.
    But the problem is I am keeping a list of object of all appointment items fields and I think searching that list every time will slower the application..
    What do you think?
    Patrick

    Thursday, March 22, 2012 7:34 AM
  • ItemChange event is a 'data' type event - it will fire whenever outlook decides to store changes made in UI, code, etc. to its local cache, store or exchange. It is up to outlook to decide when and how often it will fire, so yes, you could solve this problem by keepeing cache of values you care for per entry id and if event fires again, you check against your cache if values importnant for you changed, if not - return from ent handler doing nothing.

    As for DBUtils.getString method it should be like this (i completly ignore issue with iterating every single appointment and spanning thread/operating it in isolation, it is up to you to decide if you want to implement some batching, like operating on 10 items at once, etc.):

    store somehwere outlook;s Application or Session object and change

    foreach(object item in Domainsdf)

    {

    AppointmentItem ai = item as AppointmentItem;

    if(ai == null) continue;

    AppointmentContainer ac = new AppointmentContainer (ai);//stores EntryId, location, etc.

    Thread t = new Thread(_ =>

    {

    var results = DBUtils.getString(string.Format("your sql, whatever", ac.Location); 

    if(!result.ChangeNeeded)

    return;

    YourGUiMarshallForm.BeginInvoke(_ =>

    {

    //now we are back in main UI thread, we can access OOM

    Session.GetItemByItemId(ac.EntryId).Location = result.Location;

    })

    });

    }

    Everything we wrote above also should be considered (item can no longer exist, be already changed, etc.) so you must code defensivly.

    • Marked as answer by Patrick12_3 Thursday, March 22, 2012 12:08 PM
    • Unmarked as answer by Patrick12_3 Thursday, March 22, 2012 12:29 PM
    • Marked as answer by Patrick12_3 Thursday, March 22, 2012 1:42 PM
    Thursday, March 22, 2012 8:48 AM
  •  

    Hi,

    Tanks for your replay!!

    Some clarification please:

    I am new to threading so please be patient :)

    I understand from that line - YourGUiMarshallForm.BeginInvoke

    That we are back in main UI (OOM) and this is because I declared YourGUiMarshallForm in ThisAddIn class

    as system.windows.forms.form()

    And initial it in the on startup function, and now whenever I want to return to main UI

    I just need simply invoking the form!? And declaring functions in YourGUiMarshallForm is not needed I can use functions in my addin class.?

    What about the batching that you mentioned can you please post some reference?

    Tanks,

    Patrick

    Thursday, March 22, 2012 9:51 AM
  • yes, declaring methods on your form is not needed, that form acts only as a gateway to post message to mesage pump on thread where is was instantiated (here main UI thread). So you can pass there any method declared on other classes. As for batching - if you need to process large quantities of outlook items it would be best to do as little as possible rountripping to main UI thread to manipulate items, because each time you execute code in main UI user could see 'freeze' of outlook UI (for example when composing new mail character typing could become 'jerky'). Simply take more then one item (10, 100, you decide), create list of containers to pass to background thread and do your work. After that marshal back to main UI, iterate through results, grab for each item and modify it. Experiment with number of items in batch to see how UI will behave.

    Thursday, March 22, 2012 10:28 AM
  •  

    Tanks,

     

    Regarding threading:

    I have a form which i frm.show() from main UI.

    When form closing I call a function which create new appointment

    From the conversation above i understood that passing appointment Item is not allowed

     (way is that?) but what about creating one?

     

    patrick

    Thursday, March 22, 2012 10:53 AM
  • all activity that access types from Outlook.Interop* must be done in main UI thread. you cannot pass anything from that namespace to background thread, even harmless reading of string property.
    Thursday, March 22, 2012 11:48 AM
  • and if i handle form closing event from the mail UI?

    Public Class ThisAddIn
        Public Shared WithEvents MsgFrm As MSGfrm
        Private Shared Sub MsgFrm_Closing(ByVal myObject As Object, ByVal myEventArgs As EventArgs) Handles MsgFrm.FormClosing
            If MsgFrm.DialogResult = Windows.Forms.DialogResult.OK Then
                For Each obj As Outlook.AppointmentItem In OutlookFolder
                    obj.Location = "srt"
                    obj.Save()
                Next
            End If
            MsgFrm = Nothing
        End Sub
    End Class


    • Edited by Patrick12_3 Thursday, March 22, 2012 12:31 PM
    Thursday, March 22, 2012 12:29 PM
  • form closing event will be in main UI thread, it is ok. Next time when in doubt either check VS debugger (threads window) or check Thread.CurrentThread.ManagedThreadId against one that you can check in addin startup event.
    Thursday, March 22, 2012 12:33 PM
  •  

    Tanks very much Damian and Ken i appreciate it alot 

    Patrick

    Thursday, March 22, 2012 12:43 PM
  • no problem, close the thread if everything is clear :)
    Thursday, March 22, 2012 1:12 PM