bisquic Posted January 7, 2004 Posted January 7, 2004 Ok, I've read a handful of articles out there concerning the issue of accessing a control from a thread other than the one from which it was created. I think the most well-rounded solution I found is to create a delegate to a function that accesses the control and then call this->Invoke( theDelegate ) from the worker thread in the form to marshal the call to the UI thread. I wrote a test app to try this out and the problem I'm having is that if I try the following, the function that should be called via Invoke never gets called, and the code that follows the Invoke in ThreadLoop never gets executed... ... __delegate void DisableButtonDelegate(); DisableButtonDelegate* disableButton; Button* aButton; Thread* aThread; View() { InitializeComponent(); disableButton = new DisableButtonDelegate( this, DisableButton ); aThread = new Thread( new ThreadStart( this, ThreadLoop )); aThread->IsBackground = true; aThread->Start(); } void ThreadLoop() { while ( true ) { this->Invoke( disableButton ); MessageBox::Show( S"After invoke" ); aThread->Sleep( 2000 ); } } void DisableButton() { MessageBox::Show( S"Disabling button" ); aButton->Enabled = false; } ... However, if I place the aThread->Sleep BEFORE the Invoke, or if I insert a MessageBox before the Invoke, everything works fine and executes like I would expect it to. Can anybody with a deeper understanding of what's happening explain this to me? Also, is there a prefered way of dealing with this situation? Quote
*Gurus* divil Posted January 7, 2004 *Gurus* Posted January 7, 2004 The first thing I noticed is that you're creating the delegate once, at the beginning and then using it later on. While I can't see what difference this could make I've always created it on-the-fly as I call Invoke. As I said, this probably won't make a difference though. The other thing is that there should always be a call to inspect the InvokeRequired property before you Invoke. It's surprising how often this comes back False even when accessing from another thread. if (this->InvokeRequired) this->Invoke(disableButton); else DisableButton(); Quote MVP, Visual Developer - .NET Now you see why evil will always triumph - because good is dumb. My free .NET Windows Forms Controls and Articles
bisquic Posted January 7, 2004 Author Posted January 7, 2004 Adding that condition did the trick. I don't understand why though since I am accessing the method from a worker thread. Seems very strange to me. It's also kind of annoying to have to do that since the condition needs to be checked in every iteration of the loop. Seems like it could really affect performance in a more demanding situation... like processing collision detection. Is there any better way to do this or is this the typical practice? Thanks for the help! Quote
*Gurus* divil Posted January 7, 2004 *Gurus* Posted January 7, 2004 This is the typical practice. One could argue that if this were a demanding situation, you wouldn't be working with winforms controls in a loop :) Quote MVP, Visual Developer - .NET Now you see why evil will always triumph - because good is dumb. My free .NET Windows Forms Controls and Articles
bisquic Posted January 7, 2004 Author Posted January 7, 2004 good point :) thanks for the help! Quote
bisquic Posted January 9, 2004 Author Posted January 9, 2004 Ok, turns out the problem was not solved after all. InvokeRequired kept returning intermittent results which caused bizarre, random behavior. After a couple of hours of debugging, I finally realized what was wrong. After figuring it out, it doesn't seem like it should have taken me so long! Anyway, I was starting the thread from the View constructor and starting the app like this: View* view = new View(); Application::Run( view ); So there was a race condition where sometimes the new thread was executing before the window was even shown. I got around this by starting the thread from a button click instead. In this situation it's an acceptable solution, but I would still like to learn if there is a way to automatically start a thread in the main form after Application::Run. Since Application::Run blocks, I can't manually make a function call on the next line to do it. Anybody have any ideas? Quote
bisquic Posted January 9, 2004 Author Posted January 9, 2004 Hopefully this will put this issue to bed. Somebody clued me in on the HandleCreated event. Now I'm just registering this event in the View constructor to call a function to start the thread. If somebody sees a problem with this approach, please let me know. Otherwise I'll assume this problem has been solved. Hope these posts save somebody else a headache or two. Quote
Malfunction Posted January 9, 2004 Posted January 9, 2004 just curious but what do you use multihtreading for? I know if you got an demanding operation that can be executed paralel you do it in a thread. Now I'm thinking about not so obvious uses for threads that I didn't think of that can be very usefull. Like an autosave function would I rather use a thread (like a daemon) or a timer? Quote Debug me...
*Gurus* divil Posted January 9, 2004 *Gurus* Posted January 9, 2004 Yes, this could theoretically cause a problem. The handlecreated event can fire more than once, as controls handles are often recreated on the fly as their properties change. To get around this, make sure you check the RecreatingHandle property in the event - make sure it's false if you're starting your thread. You could also use the Application.Idle event which is fired whenever your application enters its idle state after processing. You'd have to make sure you unhooked the event as soon as it was first fired though. Quote MVP, Visual Developer - .NET Now you see why evil will always triumph - because good is dumb. My free .NET Windows Forms Controls and Articles
bisquic Posted January 9, 2004 Author Posted January 9, 2004 "You'd have to make sure you unhooked the event as soon as it was first fired though." Couldn't I just do the same with the HandleCreated event, thereby avoiding the check on the RecreatingHandle property and preventing the StartThread function from needlessly being recalled in the first place? Malfunction, I'm using multithreading to... well, currently I'm just doing this for a .NET learning experience, but you might want to use it to allow the user to ineract with the UI while some other task in your app is taking a long time to finish. Like a database request. Quote
*Gurus* divil Posted January 9, 2004 *Gurus* Posted January 9, 2004 Yeah, sure. Quote MVP, Visual Developer - .NET Now you see why evil will always triumph - because good is dumb. My free .NET Windows Forms Controls and Articles
bisquic Posted January 10, 2004 Author Posted January 10, 2004 cool, thanks again for the help! Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.