Can you raise an event that doesn't block?

  • Thread starter Thread starter Benny Raymond
  • Start date Start date
B

Benny Raymond

I understand that you would normally want to raise an event on the same
thread so that your code blocks until the event has been dealt with...
however i've run into a situation where I have a thread running that
isn't my form thread and I want it to raise a custom event and continue
going without waiting for that event to be handled...

Is this at all possible?
 
Benny,

Sure. Just use the BeginInvoke method on the event, which will spawn off a
thread pool thread to service this call, and your thread will go on its
marry way. Just remember that if there are more than one items in the
invocation list inside of the event (see GetInvocationList of the event)
that you have to iterate through them all and call BeginInvoke on each.

Dave
 
It seems to be working - but since I don't understand it that much I'd
like for you to look at the code and let me know I did it right... thanks!

// My event thing
#region Delegates and Event Declarations > MessageReceived Event
public delegate void MessageReceivedEventHandler( ServerMessage sMsg );
public event MessageReceivedEventHandler MessageReceived;
private void OnMessageReceived( ServerMessage sMsg )
{
if ( MessageReceived != null )
{
IEnumerator myEnum = MessageReceived.GetInvocationList().GetEnumerator();
while ( myEnum.MoveNext() )
{
( (MessageReceivedEventHandler) myEnum.Current ).BeginInvoke(sMsg, null,
null);
}
}
}
#endregion

// Inside the thread that loops round and round,
// if a message is recieved in this loop, it runs this line:
OnMessageReceived( message );
 
Just remember that if there are more than one items in the
invocation list inside of the event (see GetInvocationList of the event)
that you have to iterate through them all and call BeginInvoke on each.
I think you don't need that unless you want _each_ registered handler
to be run on its own thread. Normally, calling BeginInvoke on the event
is adequate, and all the registered handlers will be invoked
sequecially on a same separate thread.

Thi
 
Sequencially is fine... but now i run into another problem...

inside of the function that gets fired in the main form when the said
event takes place, I have the following line:
if (this.txt_log.Text.Length > 1024*60)

it's checking to see if the text in a rich text box is larger than 60k.
After the event is fired a bunch of times really fast, the program
crashes here and returns just after the
System.Windows.Forms.Application.Run(new frmMain());
inside Main saying that an Object Refrence not set to an instance of an
object... I had to step through my code to find out where it was
crashing... and when I mouse over .Length while stepping through Visual
Studio freezes up for a good 10 seconds and then displays no information
about .Length

Any idea what would be causing this?
 
Here's the full stack trace:

Unhandled Exception: System.NullReferenceException: Object reference not
set to an instance of an object.
at System.Windows.Forms.RichTextBox.EditStreamProc(IntPtr dwCookie,
IntPtr buf, Int32 cb, Int32& transferred)
at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr
wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
at System.Windows.Forms.Control.DefWndProc(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.TextBoxBase.WndProc(Message& m)
at System.Windows.Forms.RichTextBox.WndProc(Message& m)
at System.Windows.Forms.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd,
Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.PeekMessage(MSG& msg,
HandleRef hwnd, Int32 msgMin, Int32 msgMax, Int32 remove)
at SystThe program '[3208] TKSAdmin.exe' has exited with code 0 (0x0).
em.Windows.Forms.ComponentManager.System.Windows.Forms.UnsafeNativeMethods+IMsoComponentManager.FPushMessageLoop(Int32
dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.ThreadContext.RunMessageLoopInner(Int32
reason, ApplicationContext context)
at System.Windows.Forms.ThreadContext.RunMessageLoop(Int32 reason,
ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at TKSAdmin.Globals.Main()
 
Looks like it has something to do with modifying the text box in more
than one thread... i put this at the top which stoped the crash except
for when I click in the box while a line up updates is waiting to
happen.... am i doomed?:

private bool processMessage = false;
private void _client_MessageReceived(TaskulonServerCom.ServerMessage sMsg)
{
while (processMessage)
{
System.Threading.Thread.Sleep(1);
}
processMessage = true;
// bunch of code to do stuff with the text box
processMessage = false;
}

Benny said:
Here's the full stack trace:

Unhandled Exception: System.NullReferenceException: Object reference not
set to an instance of an object.
at System.Windows.Forms.RichTextBox.EditStreamProc(IntPtr dwCookie,
IntPtr buf, Int32 cb, Int32& transferred)
at System.Windows.Forms.UnsafeNativeMethods.CallWindowProc(IntPtr
wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at System.Windows.Forms.NativeWindow.DefWndProc(Message& m)
at System.Windows.Forms.Control.DefWndProc(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.TextBoxBase.WndProc(Message& m)
at System.Windows.Forms.RichTextBox.WndProc(Message& m)
at System.Windows.Forms.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd,
Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.PeekMessage(MSG& msg,
HandleRef hwnd, Int32 msgMin, Int32 msgMax, Int32 remove)
at SystThe program '[3208] TKSAdmin.exe' has exited with code 0 (0x0).
em.Windows.Forms.ComponentManager.System.Windows.Forms.UnsafeNativeMethods+IMsoComponentManager.FPushMessageLoop(Int32
dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.ThreadContext.RunMessageLoopInner(Int32
reason, ApplicationContext context)
at System.Windows.Forms.ThreadContext.RunMessageLoop(Int32 reason,
ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at TKSAdmin.Globals.Main()

Benny said:
Sequencially is fine... but now i run into another problem...

inside of the function that gets fired in the main form when the said
event takes place, I have the following line:
if (this.txt_log.Text.Length > 1024*60)

it's checking to see if the text in a rich text box is larger than
60k. After the event is fired a bunch of times really fast, the
program crashes here and returns just after the
System.Windows.Forms.Application.Run(new frmMain());
inside Main saying that an Object Refrence not set to an instance of
an object... I had to step through my code to find out where it was
crashing... and when I mouse over .Length while stepping through
Visual Studio freezes up for a good 10 seconds and then displays no
information about .Length

Any idea what would be causing this?
 
Looks like it has something to do with modifying the text box in more
than one thread
Yes, might be so. When you update Windows Form UI from other thread,
you need to use Control.Invoke or Control.BeginInvoke to marshall the
call to the thread that created the control. That limitation is due to
STA model of windows system.
I post a sample, note that I did not test it yet, just want to give the
idea.
private int GetTextLength(Control ctrl)
{
if (!ctrl.InvokedRequired)
{
return ctrl.Text.Length;
}
else
{
return (int) ctrl.Invoke(new TextLengthDelegate(GetTextLength),
new object[] {ctrl});
}
}
 
Oh, and you need to declare TextLengthDelegate also:
public delegate int TextLengthDelegate(Control ctrol);

Hope it helps,
Thi
 
I was doing a lot more than just testing textlength... I ended up trying
this line inside of my triggered event instead:
this.txt_log.Invoke( ProcessMessage, new object[] { sMsg } );

Where ProcessMessage was another event, and this line forces the message
to be run on the txt_log thread, making everything ok :)

Thank you both for your help in pointing me in the right direction!
 
D. Yates said:
Benny,

Sure. Just use the BeginInvoke method on the event, which will spawn
off a thread pool thread to service this call, and your thread will
go on its marry way. Just remember that if there are more than one
items in the invocation list inside of the event (see
GetInvocationList of the event) that you have to iterate through them
all and call BeginInvoke on each.

Yes, but the documentation does say that you should call EndInvoke at
some point (to do clean up), which means that it is not entirely
fire-and-forget. To have fire-and-forget you must add [OneWay] to the
method that is called through the delegate.

Richard
 
So where should the [OneWay] go? Here's my delegate/event:


#region Delegates and Event Declarations > MessageReceived Event
public delegate void MessageReceivedEventHandler(ServerMessage sMsg);

public event MessageReceivedEventHandler MessageReceived;
// [OneWay] goes here?
private void OnMessageReceived(ServerMessage sMsg)
{
if (MessageReceived != null)
{
MessageReceived.BeginInvoke(sMsg, null, null);
}
}
#endregion

Richard said:
D. Yates said:
Benny,

Sure. Just use the BeginInvoke method on the event, which will spawn
off a thread pool thread to service this call, and your thread will
go on its marry way. Just remember that if there are more than one
items in the invocation list inside of the event (see
GetInvocationList of the event) that you have to iterate through them
all and call BeginInvoke on each.


Yes, but the documentation does say that you should call EndInvoke at
some point (to do clean up), which means that it is not entirely
fire-and-forget. To have fire-and-forget you must add [OneWay] to the
method that is called through the delegate.

Richard
 
Benny said:
So where should the [OneWay] go? Here's my delegate/event:
public delegate void MessageReceivedEventHandler(ServerMessage sMsg);

public event MessageReceivedEventHandler MessageReceived;
// [OneWay] goes here?
private void OnMessageReceived(ServerMessage sMsg)
{
if (MessageReceived != null)
{
MessageReceived.BeginInvoke(sMsg, null, null);

It should go on whatever method(s) that MessageReceived contain. Yes I
know it is a pain if you don't own that method. The solution there is to
write a wrapper method that calls the method synchronously and put
[OneWay] on the wrapper method and call the wrapper method
asynchronously.

[OneWay] void Wrapper(int param)
{
MethodIDontOwn();
}

delegate void MyDelegate(int param);

MyDelegate del = new MyDelegate(Wrapper);
del.BeginInvoke(param, null, null);
// don't need to call EndInvoke

Richard
 
Back
Top