views:

3754

answers:

6

Hi all,

I have a .NET class library containing a class with a method that performs some lengthy operation. When a client calls this method it should perform the lengthy operation on a new thread in order to avoid blocking the caller. But once the method finishes it should execute some code on the main thread. In a WinForms application I could have used the System.Windows.Forms.Control.Invoke method but this is not my case. So how can I achieve this in C#?

+3  A: 

If a thread has to be able to execute some bit of code (usually in the form of a delegate) posted to it by another thread, it will have to basically be waiting for those instructions. What else is your main thread doing? It's not that hard to build an equivalent of the event loop (basically you'd have a producer/consumer queue of delegates) but you need to be aware that you can't just interrupt the main thread and say "do this now".

Why does this have to execute on the main thread?

Jon Skeet
How about using a dispatcher (see my answer below). As to why this might be necessary, I had to do something like this when dealing with remoting interfaces calling into legacy code.
cdiggins
+1  A: 

A thread cannot just execute stuff on another thread. The closest you can get is to put a delegate on a queue for the other thread to execute, but that assumes that the other thread is cooperating about this.

In a WinForms application, the main loop looks for such queued messages on each loop iteration.

If you just need to communicate the finishing of the worker thread, you can use e.g. a flag variabe. If the main thread should be able to wait for job termination, use a semaphore or condition variable (monitor).

Bjarke Ebert
A: 

Here's my exact scenario: I have a .NET class library exposed as a COM object (using regasm.exe). The COM object contains a method that runs an external application (using Process.Start). The COM object is used in Internet Explorer. So my web page runs the external application and I need to find a way to pass the ExitCode to the web page.

At first I didn't start the external application on a new thread and just waited for the user to close the application and then my function returned the ExitCode to the caller. But while the application was running IE was not responsive. So I decided to start the application in a new thread but now I can no longer return the ExitCode.

The COM object is created with: new ActiveXObject instruction and unfortunately javascript doesn't support event sinking so I can't write an event in C# that's triggered when the application exits.

Darin Dimitrov
+1  A: 

There's no way to explicitly make code run on a specific thread, (except for the thread that was used to create UI Controls - this is an exception) but if you simply want to call code other than the code when a thread completes, You use delegates.
First declare a delegate with the signature of the method you want to run on the new thread...

public delegate bool CheckPrimeDelegate(long n);

Then in your code, create an instance of the delegate, call it using BeginInvoke (passing any parameters it needs) and PASS a callback function delegate (OnChkPrimeDone)

class MyClassApp
{
   static void Main() 
   {
      CheckPrimeDelegate ckPrimDel = new CheckPrimeDelegate(Prime.Check);

      // Initiate the operation
      ckPrimDel.BeginInvoke(4501232117, new AsyncCallback(OnChkPrimeDone), null);

      // go do something else . . . .      
   }

   static void OnChkPrimeDone( IAsyncResult iAr)
   {
        AsyncResult ar = iAr as AsynchResult;
         CheckPrimeDelegate ckPrimDel = ar.AsyncDelegate as CheckPrimeDelegate;
         bool isPrime = ckPrimDel.EndInvoke(ar);
         Console.WriteLine(" Number is " + (isPrime? "prime ": "not prime");
   }
}

When it is done, it will call the callback function (OnChkPrimeDone)

If you explicitly need to run this callback function on the thread that was used to create the COM Active-X object, then check the .Net Managed Code wrapper variable that holds a reference to this object... If it has a method called InvokeRequired(), then, in your callback function, test the boolean return value of this method.
If it has InvokeRequired() method and it returns true, then the active-X object will also expose a "BeginInvoke()" Method. Then, Create ANOTHER Delegate, populated with the same function, and call BeginInvoke on the Active-X Object, passing it this new delegate... It will then run on the same thread as was used to create teh Active-X Object

If (MyActiveXObject.InvokeRequired())
     MyActiveXObject.BeginInvoke(...);
Charles Bretana
"There's no way to explicitly make code run on a specific thread", you can use a Dispatcher object. This makes it relatively easy to do so.
cdiggins
+3  A: 

I found a simple solution to the problem :

My COM object is declared like this:

public class Runner
{
    public void Run(string executable, object processExitHandler)
    {
        ThreadPool.QueueUserWorkItem(state =>
        {
            var p = new Process()
            {
                StartInfo = new ProcessStartInfo()
                {
                    FileName = executable
                }
            };
            p.Start();
            while (!p.HasExited)
            {
                Thread.Sleep(100);
            }

            state
                .GetType()
                .InvokeMember(
                    "call", 
                    BindingFlags.InvokeMethod, 
                    null, 
                    state, 
                    new object[] { null, p.ExitCode }
                );
        }, processExitHandler);
    }
}

And in my HTML page I use it like this:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"&gt;
<html>
<head><title>ActiveXRunner</title>    
    <script type="text/javascript">
    function runNotepad() {
        var ax = new ActiveXObject('ActiveXRunner.Runner');
        ax.Run('c:\\windows\\notepad.exe', h);
    }

    function h(exitCode) {
        alert('exitCode = ' + exitCode);
    }
    </script>
</head>
<body>
    <a href="#" onclick="runNotepad();">Run notepad and show exit code when finished</a>
</body>
</html>
Darin Dimitrov
A: 

You can invoke a function on a specific thread by using a System.Windows.Threading.Dispatcher object (from the WindowsBase assembly).

For example:

public class ClassCreatedBySomeThread
{
    Dispatcher dispatcher = Dispatcher.Current; 

    public void SafelyCallMeFromAnyThread(Action a)
    {
       dispatcher.Invoke(a);
    }
} 
cdiggins