views:

638

answers:

4

I'm writing a plug-in for another program which uses the native program to open a series of files to extract some data from. One problem I am having is the process takes a long time and I want to keep the user interface from hanging. Plus I also want to give the user the ability to cancel the process before it completes. In the past I've used a background worker for this type of thing, but in this case I don't think a BackgroundWorker will work.

To create a plug-in through the API I am using one can create a custom command by inheriting from an IAPICommand interface. This interface includes an Execute(Application app) method. The class is then instantiated and the Execute() method is called by the program when the user evokes the custom command in the program.

The Execute() method is passed a reference to the current Application object when it is called, and it is this application object that is used to open the files to extract data from. However, the application instance is not able to open a document when requested by a thread other the the original Execute() thread.

So typically the UI would exist on the main thread, and the time consuming data extraction would be performed on a secondary thread. However, in this case the data extraction must be performed on the main thread, and I need to create a secondary thread for the UI.

Here's a stripped down version of the code.

class MyCommand:IAPICommand
{
    public void Execute(Application app) // method from IAPICommand
    {
        Thread threadTwo= new Thread(ShowFormMethod);
        threadTwo.Start();
    }

    public void ProcessWidget(Widget w, Application app)
    { 
        //uses an App to work some magic on C
        //app must be called from the original thread that called ExecuteCommand()
    }

    //method to open custom form on a seperatethread
    public void ShowFormMethod()
    {
      MyForm form = new MyForm();
      form.ShowDialog();  
    }
}

Here is a flow chart that shows how I think this should ultimately work.

alt text

  1. Does this diagram make any sense, and if so am I even taking the correct approach to solve this problem?
  2. Once the main thread starts the UI thread I want it to wait for the user to either select widgets to process, or end the command by closing the form (the red figures on the diagram). How can I make the main thread wait, and how do I trigger it to continue either with processing or to continue to the end when the UI thread ends? I was thinking I could have the main thread wait on a Monitor lock. The UI thread would then populate a static list of Widgets to be processed, and then pulse the main thread to trigger the processing. The UI thread would also pulse the Main thread when the form is closed, and the main thread would know to continue to the end of the command if it was ever pulsed when the list of widgets to process was empty.
  3. How do I allow the main thread to communicate the progress or completion of widget processing back to the UI thread (yellow arrows in the diagram)? Do I just used the BeginInvoke() method of the Form to do this?
  4. How do I allow the UI thread to cancel the widget processing (green arrow in the diagram)? I think I could just setup a static Boolean flag that is checked before each widget is processed?
+1  A: 

I assume that the host application is a WinForms app.

You need to save the SynchronizationContext from the original thread in your Execute method, then call its Send method to execute code on the host's UI thread.

For example:

class MyCommand:IAPICommand
{
    SynchronzationContext hostContext;
    public void Execute(Application app) // method from IAPICommand
    {
        hostContext = SynchronzationContext.Current;
        Thread threadTwo = new Thread(ShowFormMethod);
        threadTwo.Start();
    }

    public void ProcessWidget(Widget w, Application app)
    { 
        //uses an App to work some magic on C
        //app must be called from the original thread that called ExecuteCommand()
        SomeType someData = null;
        hostContext.Send(delegate { someData = app.SomeMethod(); }, null);
    }
}
SLaks
Ah ok, but how does the form get at "ProcessWidget()" and or hostContext? Don't I need to somehow pass a reference to one of these when I'm creating the form on the other thread?
Eric Anastas
Yes. You can pass the instance of your `MyCommand` class to the form.
SLaks
Alright I'm starting to figure this out. How do I get the original thread to wait for the secondary form thread? In reality Execute() returns an enum that communications if the command succeeded. How do I get the main Execute() thread to wait for a request from the FormThread and then reroute itself to the ProcessWidget() method? I don't want the Execute() thread returning to the main program until the user has finished with the Form thread.
Eric Anastas
Alright I'm trying to make this work as you describe with the SynchronizationContext. I have a breakpoint at my hostContext.Send() command and at the top of SomeMethod(). The program reaches the call to hostContext.Send() but once called the program seems to just stop. It never hits the breakpoint in SomeMethod() nor does it hit the breakpoint after the hostContext.Send() line.
Eric Anastas
+15  A: 

It's generally a bad idea to have multiple threads in your application that each create forms. It isn't impossible to make this work, but it's much harder than you think it will be because forms that are in a parent-child relationship send messages to each other, and when they do, the one sending the message blocks until the one receiving handles it.

Mix this in with the message passing or synchronization between threads that you are doing explicitly, and it's easy to end up with deadlocks. So, in general, you are better off making sure that you reserve your main thread for your user interface, and do all processing in other threads that have no UI.

If you conform to that design, then the background threads can use Control.BeginInvoke to pass messages to the UI thread without having to wait for the messages to be processed.

John Knoeller
I understand what you are saying but because my program is a plug-in for another program I don't really have controlof what thread the "processing" is done in. It has to be done in the thread of the plug-in, so in order to separate the UI from the processing the UI has to go in the secondary thread.
Eric Anastas
@eric: sucks to be you then. Control.BeginInvoke still gets you from your background thread to your UI thread without waiting, the more you can avoid having the background thread wait for the UI thread the better off you will be. Can't deadlock if one of your threads never waits.
John Knoeller
Oh and maybe you misunderstood my problem. I don't have forms or any UI elements being created in multiple threads. I only have one form which is created in the second thread because I need the first "main" thread" for the processing as it is the only thread that can access the parent application.
Eric Anastas
No I understand, there are two threads, both of which have forms; the app thread and your form thread. You are using the app thread as a processing thread (for you it's a 'background' thread because your UI is on the other thread). This is the maximally bad design, but I'm sure you know your situation and have no choice in the matter, so sux to be you...
John Knoeller
By the way, this isn't an unusual situation for plugin vendors. A lot of VST (audio) plugin vendors have this exact problem (in C++, not C#, but still the same threading issues)
John Knoeller
+2  A: 

In addition to the other answers, I recommend that you use a callback method from ProcessWidget to pass progress back to the calling thread. To prematurely stop the worker thread, you can use the callback to return a halt signal to your worker thread if it updates the caller often enough. Or use a separate callback method to periodically check for go/no-go. Or set a (gasp!) global static flag that the worker periodically checks. Or call Thread.Abort on the worker thread and have it catch the ThreadAbortException to clean up any resources.

ebpower
A: 

If you look at Java swing, it is a nice example of how to do this:

1) A main thread is responsible for handling all UI requests. This removes any race conditions from the app.

2) Any time any "work" is to be done, spawn a thread (or a thread pool) and do the work. Thus the main thread is not held up except for a few microseconds and the UI is completely responsive while whatever is going on.

3) In all languages there has to be a thread interrupt mechanism. In java you invoke .interrupt() on the thread, and the current running thread gets a InterruptedException thrown wherever it is executing. You job is to catch that exception, figure out if you are really interrupted (read javadocs for this part) and if you are just let yourself die (return out of the run method).

1 + 2 = unobtrusive client interaction

3 = killing threads

An alternative to 3 (if 3 is too complex) is to give the thread a method .kill(); the method sets a kill flag. When you are reading a buffer from the hard drive in a loop, check if the kill flag is set, if it is then break out of the loop, close handlers, and return out of the run method.

Edit: sorry forgot to mention progress report:

Your thread should have a publicly exposed thread-safe method of getting the "progress report" or rather a data structure containing information about progress. Your UI thread should periodically (say every .5 seconds) check the thread's progress report and update the UI's progress bar. And by UI thread checking I mean your widget that shows the progress makes a request to re-render with the latest information on a timer, until done.

Dmitriy Likhten