Multithreading with C#

  • Thread starter Thread starter Dorian
  • Start date Start date
D

Dorian

I've looked over a few posts on multi threading with the OOM and I understand
that the OOM itself is restricted to being single threaded. I could use some
information on how this works.

What I currently have is a MailItem object being passed to a new background
thread every time an event occurs (Explorer.SelectionChange for example). In
the thread I access things like SenderEmailAddress, ReplyRecipients. Is this
thread safe?

What happens if I access a OOM object from the worker thread that wasn't
explicitly passed to it (Example: changing the visibility of a button on a
ribbon bar). I'm guessing this isn't thread safe.

Is there any way to trigger an event on the main thread once the worker
thread has finished running? I am used to doing this with forms, and having a
worker thread invoke a delegate on the main form. Since we're not working
with forms, or even controls for that matter, how would I go about doing this?
 
Using the Outlook object model from anywhere but your main thread, without
marshaling calls to the object model back to that thread, is a great way to
crash or hang Outlook.

You really shouldn't be passing an Outlook object to the background thread.

What you should do is set up a struct or something with the properties you
need from that Outlook object and passing the struct to your background
worker thread. That way all calls to the object model are done from the main
thread.

I use delegate event handlers in my main code to receive events fired by the
worker thread when the work is finished.
 
Using a struct for the MailItem information makes sense, and if I am able to,
using a delegate event handler to return to the main thread makes sense. In
my previous experiences I've used the Invoke method of the control that the
main thread is being run on. Unfortunately I don't use any forms in the my
addin. Any hints on how to invoke an event on a main thread without having
access to Control.Invoke?
 
Something like this for the delegate events:

// In the class for the background worker:

internal delegate void HelloUserDoneEventHandler(object sender,
EventArgs e);

internal event HelloUserDoneEventHandler HelloUserDone;

// make a call in the code that handles worker done to OnHelloUserDone()

protected virtual void OnHelloUserDone(EventArgs e)
{

HelloUserDoneEventHandler handler = HelloUserDone;
if (handler != null)
{
// Invokes the delegate.
handler(this, e);
}
}

// in the code in the main class or wherever

private void HelloUser(object sender, EventArgs e)
{
if (e.Result != null)
{
// event fired, work done
}
}

Of course in the main code you'd need to instantiate the HelloUser event
handler, something like this, where myWorker is the class that has the
background worker:

_worker.HelloUserDone += new myWorker.HelloUserDoneEventHandler(HelloUser);
 
That's exactly what I have, but when the event is fired, and I breakpoint the
event code, visual studio shows it's still running in the worker thread. Is
VS being a little misleading in it's thread monitoring?

One thing I want to be clear about in your example: the OnHelloUserDone
method is actually called on the worker thread, correct?
 
I noticed that the only difference was that you mentioned the worker thread
should maintain a separate class. I think that should make the difference.
I'll make some changes and check in again. Thanks for the help so far, it's
much appreciated.
 
Putting it in a different class doesn't fix the problem. The event gets
triggered on the worker thread.
 
OnHelloUserDone() is a virtual method in the worker class. It calls to any
event handlers that match the signature of the HelloUserDoneEventHandler
delegate. Those handlers are in other classes and have registered to handle
the event. If they do not match the signature or haven't registered for the
event they won't get called.
 
The work complete event fires in the worker thread. Then you call the
virtual event handler in the worker thread. That calls the delegates which
are located in other classes. The delegates must be registered to handle the
event.
 
Ok, so here's what I do:

// Main Class
public partial class ThisAddIn
{
private void Explorer_SelectionChange()
{
ReceivedMailHandler rmh = new ReceivedMailHandler();
// Do some stuff (Add data to the object)

rmh.SupportExceptionThrown += new
ReceivedMailHandler.SupportExceptionDelegate(rmh_SupportExceptionThrown);
rmh.SupportIgnoreChanged += new
ReceivedMailHandler.SupportIgnoreDelegate(rmh_SupportIgnoreChanged);

Thread th = new Thread(rmh.ParseAndInsertReceivedMail);
th.IsBackground = true;
th.Start();
}

void rmh_SupportIgnoreChanged(bool IsVisible)
{
btnExpIgnore.Visible = IsVisible; //Breakpoint here
}

void rmh_SupportExceptionThrown(Exception ex)
{
string blah = "asd"; //Breakpoint here
}
}

// Worker Class
class ReceivedMailHandler
{
public delegate void SupportExceptionDelegate(Exception ex);
public delegate void SupportIgnoreDelegate(bool IsVisible);

public void ParseAndInsertReceivedMail()
{
// Do work (no OOM objects)
RaiseIgnoreChange(true);
RaiseException(new Exception("blah"));
}

private void RaiseException(Exception ex)
{
SupportExceptionDelegate sed = SupportExceptionThrown;
if (sed != null) sed(ex);
}

private void RaiseIgnoreChange(bool isVisible)
{
SupportIgnoreDelegate sid = SupportIgnoreChanged;
if (sid != null) sid(isVisible);
}
}

When it hits the breakpoints on rmh_SupportIgnoreChanged and
rmh_SupportExceptionThrown, both are still in the worker thread.
 
You're not following the signature patterns of what I showed you, nor are
you calling to a protected virtual handler that in turns calls the delegate
event handlers.

You need to follow the signatures, where you will receive sender and an args
array in your delegate event handlers. To add a string like blah to the
event args you would create an args[] array and populate args[1] with blah.

You're trying to handle this like a normal raising event type thing and that
won't do what you want.
 
So this is what I have now. I've changed all of the accessors for the event,
delegate and methods. I've created a new class that implements the EventArgs
class. I don't know where you were going with the args array thing, but in
all the examples I've seen, if you want to include custom data in an event
argument you implement Eventargs. I've gotten my example as close as I can to
yours and it's still coming back on a different thread. I am currently
calling a protected virtual method that in turn calls the delegate. Don't
worry, I'd be frustrated with me too :-)

// Main Class
public partial class ThisAddIn
{
private void Explorer_SelectionChange()
{
ReceivedMailHandler rmh = new ReceivedMailHandler();
// Do some stuff (Add data to the object)

rmh.SupportIgnoreChanged += new
ReceivedMailHandler. SupportIgnoreEventHandler(rmh_SupportIgnoreChanged);

Thread th = new Thread(rmh.ParseAndInsertReceivedMail);
th.IsBackground = true;
th.Start();
}

void rmh_SupportIgnoreChanged(Object sender,
ReceivedMailHandler.IgnoreEventArgs e) //Relates to your HelloUser
{
btnExpIgnore.Visible = e.visible; //Breakpoint here
}
}

// Worker Class
class ReceivedMailHandler
{
internal delegate void SupportIgnoreEventHandler(object Sender,
IgnoreEventArgs e); // Relates to your HelloUserDoneEventHandler
internal event SupportIgnoreDelegate SupportIgnoreChanged; // Relates to
your HelloUserDone

public void ParseAndInsertReceivedMail()
{
// Do work (no OOM objects)
RaiseIgnoreChange(new IgnoreEventArgs(true));
}

protected virtual void RaiseIgnoreChange(IgnoreEventArgs e) //Relates to
your OnHelloUserDone
{
SupportIgnoreEventHandler sid = SupportIgnoreChanged;
if (sid != null) sid(this, e);
}

public class IgnoreEventArgs : EventArgs
{
public bool visible;

public IgnoreEventArgs(bool Visible)
{
this.visible = Visible;
}
}
}
 
I'm not sure if this will make any difference, I'm by no means the great C#
expert, and we've strayed a bit from Outlook code but you have this line in
your worker class:

internal event SupportIgnoreDelegate SupportIgnoreChanged; // Relates to
your HelloUserDone

I have the equivalent line in my code as:

internal event SupportIgnoreEventHandler SupportIgnoreChanged;

Then in my work completed event handler I have the equivalent of this:

public void ParseAndInsertReceivedMail()
{
// Do work (no OOM objects)
OnSupportIgnoreChanged(new IgnoreEventArgs(true));
}

protected virtual void OnSupportIgnoreChanged(IgnoreEventArgs e)
//Relates to
your OnHelloUserDone
{
SupportIgnoreEventHandler sid = SupportIgnoreChanged;
if (sid != null) sid(this, e);
}

Again, I'm no C# great expert but to me the naming differences and the
declaration syntax of the event are important in getting things to work.

If my suggestions don't help it might be more fruitful to post in a group
dedicated to C# where people more expert in the ins and outs of C# are
likely to hang out.
 
Woops! Yeah I was doing some editting in the posting textarea and forgot to
change the name of that delegate. It should be named the same as yours. Even
with these changes it's still coming back on the worker thread. I wouldn't
call myself a c# expert either. I am also fluent in VB if that makes things
easier (doubtful).

Your above example definitely returns to the main thread? I'm still boggled
as to why yours works and mine doesn't.
 
What I'm using initially fires in the worker class and then the event in the
Connect class fires.

About the only difference is that I used a form with a background worker on
it rather than setting up a separate thread. I preferred to do it that way,
you might want to try that.

My expertise in VB isn't much help with things like delegate events in C#,
my C experience is much more relevant.
 
Is there any way to trigger an event on the main thread once the worker
thread has finished running? I am used to doing this with forms, and having a
worker thread invoke a delegate on the main form. Since we're not working
with forms, or even controls for that matter, how would I go about doing this?

Create a simple Windows Forms control to do the thread switching for
you.
It does not need be bound to a form.

In main thread:

Control threadMarshaler; // member field
//...
threadMarshaler = new Control();
IntPtr dontcare = threadMarshaler.Handle; // Force creation of
necessary message pump

In worker thread:

threadMarshaler.Invoke(...);
 
Back
Top