Monday, November 28, 2005
Multithreading in VS2005
As I keep saying, I'm working on a program that captures images using the Canon SDK and processes them. This weekend, I set out to add progress dialogs. So I created a single dialog, with a progress bar and properties/methods to set text and the progress state. My strategy was to do all my processing on another thread, and to have my progress dialog as a member variable of my main window. My code passes in this instance to the other thread, then calls DoModal, the idea being that the progress dialog is manipulated by the worker thread, and it's modal state stops the main thread from doing anything apart from making sure my windows are drawn properly. Part of my problem was definately not to let the user do anything while this other thread worked. I could probably have achieved this on one thread with Application.DoEvents, but this seemed better at the time.
First of all, has anyone at Redmond tried debugging a multithreaded app ? I set a break point, and when it hits, the IDE does not respond for a good two minutes, excepting the little tool tip that comes up to say it's busy, and if this happens often I should tell Microsoft. Then, when my breakpoint hits, I can do no evaluation of variables anywhere, because 'an operation has timed out, you will need to restart execution for evaluation to work'. Great. Except that when I try to step to the next line, the program never responds again, and any attempt to break into it will break into the thread that is stuck calling DoModal on my progress dialog.
All of this only after I got the code to work at all. In VS2005, the framework detects if a UI element is used across two threads, and throws an exception. To stop this behaviour, you need to set CheckForIllegalCrossThreadCalls = false;, which is a property on the Control class. I found this info easy enough. I was even able to see a mock class for the Control class when I chose 'Go to definition' on the base class of my control. I just wish that the bug I reported had been fixed ( which is that if I create a library, use it in a project that I add to the solution, and then choose go to source on a library method in the other project, instead of taking me to the code, it creates a stub class with no code in it, and I need to manually go to the actual code, which is part of the same solution and therefore should be visible ).
First of all, has anyone at Redmond tried debugging a multithreaded app ? I set a break point, and when it hits, the IDE does not respond for a good two minutes, excepting the little tool tip that comes up to say it's busy, and if this happens often I should tell Microsoft. Then, when my breakpoint hits, I can do no evaluation of variables anywhere, because 'an operation has timed out, you will need to restart execution for evaluation to work'. Great. Except that when I try to step to the next line, the program never responds again, and any attempt to break into it will break into the thread that is stuck calling DoModal on my progress dialog.
All of this only after I got the code to work at all. In VS2005, the framework detects if a UI element is used across two threads, and throws an exception. To stop this behaviour, you need to set CheckForIllegalCrossThreadCalls = false;, which is a property on the Control class. I found this info easy enough. I was even able to see a mock class for the Control class when I chose 'Go to definition' on the base class of my control. I just wish that the bug I reported had been fixed ( which is that if I create a library, use it in a project that I add to the solution, and then choose go to source on a library method in the other project, instead of taking me to the code, it creates a stub class with no code in it, and I need to manually go to the actual code, which is part of the same solution and therefore should be visible ).
Comments:
<< Home
CG, we use multithreading extensively at work (in fact, while the user is busy clicking away and selecting different items in our UI, we'll have up to 5 System.ComponentModel.BackgroundWorker objects running threads in the background.
The neat thing about BackgroundWorker is that the DoWork event will be fired on a background thread; in the DoWork event handler you'll want to do your heavy lifting: for us, that's caching data from a remote server via .NET remoting. Then magically, after the DoWork event handler returns, the BackgroundWorker's RunWorkerCompleted event gets fired: but on the UI thread! Woo, no having to do a Control.Invoke(...) or Control.BeginInvoke to get things on the right thread; it's all handled for you gracefully. I really recommend it, BAckgroundWorker is the way to go.
The way I typically do progress forms is to launch a BackgroundWorker to do the heavy lifting & call ShowDialog on the progress form, all on the UI thread.
Setup the BackgroundWorker's ProgressChanged event handler (which, nicely enough is called on the UI thread for you). In the BackgroundWorker's DoWork event handler, you can call BackgroundWorker.ReportProgress(percent, state), and will raise the ProgressChanged event for you on the UI thread. In the ProgessChanged handler, you can modify the progress bar or progress form as you please, without having to deal with Control.Invoke or Application.DoEvents or anything like that. Works nicely.
Post a Comment
The neat thing about BackgroundWorker is that the DoWork event will be fired on a background thread; in the DoWork event handler you'll want to do your heavy lifting: for us, that's caching data from a remote server via .NET remoting. Then magically, after the DoWork event handler returns, the BackgroundWorker's RunWorkerCompleted event gets fired: but on the UI thread! Woo, no having to do a Control.Invoke(...) or Control.BeginInvoke to get things on the right thread; it's all handled for you gracefully. I really recommend it, BAckgroundWorker is the way to go.
The way I typically do progress forms is to launch a BackgroundWorker to do the heavy lifting & call ShowDialog on the progress form, all on the UI thread.
Setup the BackgroundWorker's ProgressChanged event handler (which, nicely enough is called on the UI thread for you). In the BackgroundWorker's DoWork event handler, you can call BackgroundWorker.ReportProgress(percent, state), and will raise the ProgressChanged event for you on the UI thread. In the ProgessChanged handler, you can modify the progress bar or progress form as you please, without having to deal with Control.Invoke or Application.DoEvents or anything like that. Works nicely.
<< Home