views:

158

answers:

6

I have an application that needs to work with a vendor-supplied API to do Screen Pops when a call is routed to the user's extension. Another developer worked on getting the API to work and delivered a prototype wrapper class that isn't quite right, and I'm trying to get it right.

Because this API can block for several minutes before returning, the developer started the API interface code in a separate thread, like this:

// constructor
public ScreenPop( ...params... )
{
  ...
t = new Thread( StartInBackground );
t.Start();
)

private void StartInBackground()
{
  _listener = new ScreenPopTelephoneListener();
  _bAllocated = true;
  _listener.Initialize( _address );
  _bInitialized = true;
  _listener.StartListening( _extension );
  _bListening = true;
}

The call to Initialize is the one that could hang, if the phone server was unresponsive.

I don't think it was intentional on the part of the developer who did this, but when the API is initialized this way, the thread keeps running, so that now I have to stop the thread before I can clean up when I want to. I also have to use Invoke to get events from the thread, as others so kindly pointed out in my previous question, but that is working now.

Anyway, here is the logic used to shut everything down and clean up:

public void ShutdownFunc()
{
  try
  {
    if ( _bListening )
    {
      _listener.StopListening();
      _bListening = false;
    }
    if ( _bInitialized )
    {
      _listener.Shutdown();
      _bInitialized = false;
    }
    if ( _bAllocated )
    {
      _listener = null;
      _bAllocated = false;
    }
  }
}

Of course, there is a try/catch around the whole mess to prevent unhandled exceptions.

My biggest problem is, how do I call the shutdown code when the API interface code is running in a separate thread? I need to clean up in case the user logs off and then logs back on again, as the API gets confused if I don't. Thread.Abort won't work, as it's not in a place to catch the exception, and the "volatile bool" method won't work either, for obvious reasons (most of the time, the API thread is not active).

So there seems to be no way to call the shutdown logic if the API interface code is running on a separate thread. Thread.Abort() bypasses all the logic and kills the thread without cleaning up. Calling the shutdown logic from the GUI thread hangs when it calls the Shutdown method. So, what can I do?

The IDispose interface just adds another layer of abstraction in the GUI thread, and does nothing to solve the problem here.

For now, to get the rest of the code developed, I am eliminating threads completely. But, before I release the app, I need to figure this out.

Anybody?

Thanks,
Dave

+1  A: 

You may further encapsulate initialization logic and check on a ManualResetEvent in the main initialization thread while spawning a worker thread to do the actual initialization. If the ManualResetEvent is set from your main thread while you're still initializing, you can abort the thread doing the actual initialization and do your cleanup, otherwise if the worker thread completes you just stop waiting on the ManualResetEvent. It's kind of dirty and tricky, but should do what it's supposed to ;)

emaster70
That would work if the Initialize was the only thing done in the new thread. The thread keeps running, and I need to know how to stop it...
DaveN59
What I suggested involved splitting Initialize and StartListening so that after the initialization happened you can have just the background thread listening. The structure is Main -> worker -> initializer. Initializer terminates and leaves worker to listen and process the data.
emaster70
I get that, and it's a great idea, but I still have the problem of how to shut down the worker thread. I don't get that yet, and there's no way to award half an answer...
DaveN59
Please try this: ManualResetEvent in the main thread spawning a worker sharing a reference to the MRE and spawning 2 threads: one that will preform initialization and listen (let's call it T1) and one that will just wait on the MRE and when that is set will abort T1 and run the shutdown code (let's call it T2). Meanwhile you should call join on T2 from worker, so that after T2 has spawned worker will be blocked and the only way to control this will be the MRE.
emaster70
Sorry, I got called off on a rush project and haven't even thought about this for a few days. I will get abck to it this week and update this thread. Dave
DaveN59
OK, I finally got back to this. It seems to me the idea of using some kind of event to terminate the thread is appropriate here. I will be posting my solution as an answer below, and welcome any comments and/or criticisms of my approach. I am new to the .NET event model, so there may well be room for improvement...I neglected to mention the thread does nothing but wait on events from the API, so an event really makes the most sense here.
DaveN59
A: 

First of all, if you need to shut down your application and Initialize() is still running, you have only two options:

  1. Wait for 'Initialize' to finish
  2. Abort the thread that's running 'Initialize'.

Aborting the thread should always be the last option, but in case you really need to stop the application and the Initialize doesn't have a timeout parameter, there's no other way.

Also, I would suggest wrapping that 3rd party API in your own class, implementing IDisposable on it and holding the API state using some enum instead of separate bool flags like you did in ShutdownFunc. Add some simple automation machine logic into it to handle each state. And keep the possible states count to minimum needed.

Or maybe throwing that vendor's library in the garbage can ;)

Igor Brejc
First of all, the code here is the wrapper class - the calls to _listener.whatever are calls to the 3rd party API library. Secondly, I was asking if Dispose is the correct way to handle it, which is what you seem to be saying. I do like the idea of state instead of multiple bools, though
DaveN59
IDisposable is always the correct way to handle disposing of resources, but the problem is what happens if you call Dispose when your other thread is waiting for Initialize(). This is where the threead synchronization comes into play (see emaster's answer).
Igor Brejc
Like my comment to emaster, that is great if the only thing done in the new thread was Initialize. The thread keeps running, and I need to figure out how to shut it down, short of Abort.
DaveN59
This answer is the best answer to what the question seems to be. Turns out I have a different problem (if I call Initialize() on a separate thread, Shutdown() always hangs), but I used the ideas presented here to clean up my code considerably.
DaveN59
A: 

I reworked the logic for the shutdown function, to get rid of the handful of booleans floating around. Here's what the new code looks like:

private enum ApiState { Unallocated, Allocated, Initialized, Listening };
private ApiState _apiState = ApiState.Unallocated;

private void StartInBackground()
{
  _listener = new ScreenPopTelephoneListener();
  _apiState = ApiState.Allocated;
  _phoneListener.Initialize( _strAddress );
  _apiState = ApiState.Initialized;
  _phoneListener.StartListening( _intExtension.ToString() );
  _apiState = ApiState.Listening;
}

public void ShutdownFunc
{
  try
  {
    if ( ApiState.Listening == _apiState )
    {
      _listener.StopListening();
      _listener.Shutdown();
    }
    if ( ApiState.Initialized == _apiState )
    {
      _listener.Shutdown();
    }
  }
  catch {}
  finally
  {
    _listener = null;
    _apiState = ApiState.Unallocated;
  }
}

The problem remains, however, where I can't call the Shutdown logic without hanging the GUI thread. Is there any way to interrupt the worker thread using a call from the GUI thread, short of Abort? Can I possibly set up an event handler and use a manually generated event from the GUI thread to stop the worker thread? Has anyone tried this?

I haven't tried the suggestions regarding starting a new thread for the Initialization call, as that only solves part of the problem and may be unnecessary once I get the real problem resolved.

DaveN59
A: 

OK, here goes another attempt. I tried to declare an event and raise that event on the thread, to run the shutdown logic from the thread the API is running on. No luck, it still tries to run the shutdown logic on the GUI thread and hangs the application.

Here's the applicable code:

public delegate void HiPathScreenPop_ShutdownEventHandler( object parent );

class HiPathScreenPop
{
    // Shutdown event, raised to terminate API running in separate thread
    public event HiPathScreenPop_ShutdownEventHandler Shutdown;
    private Thread _th;

    //ctor
    public HiPathScreenPop()
    {
        ...
        _th = new Thread( StartInBackground );
        _th.Start();
    }

    private void StartInBackground()
    {
        _phoneListener = new ScreenPopTelephoneListener();
        _apiState = ApiState.Allocated;

        _phoneListener.StatusChanged += new _IScreenPopTelephoneListenerEvents_StatusChangedEventHandler( StatusChangedEvent );
        _phoneListener.ScreenPop += new _IScreenPopTelephoneListenerEvents_ScreenPopEventHandler( ScreenPopEvent );
        this.Shutdown += new HiPathScreenPop_ShutdownEventHandler( HiPathScreenPop_Shutdown );

        _phoneListener.Initialize( _strAddress );
        _apiState = ApiState.Initialized;

        _phoneListener.StartListening( _intExtension.ToString() );
        _apiState = ApiState.Listening;
    }

    public void KillListener()
    {
        OnShutdown();
    }

    private void OnShutdown()
    {
        if ( this.Shutdown != null )
        {
            this.Shutdown( this );
        }
    }

    void HiPathScreenPop_Shutdown( object parent )
    {
        // code from previous posts
    }
}

What seems to happen when I call KillListener is this:

1) OnShutdown() gets called on the UI thread (OK)

2) Shutdown( this ) gets called (here's where I expect an event to be raised ont the background thread

3) HiPathScreenPop_Shutdown() gets called on the UI thread, and hangs on the _phoneListener.ShutDown() call, same as if I was not using the event mechanism.

OK, so what piece(s) of magic am I missing, or completely misunderstanding? It seems all the plumbing I created did nothing much except clutter up the code...

Thanks for any reponses, Dave

DaveN59
A: 

When you call an event handler directly from Thread A, all the handlers for that event are also executed on Thread A. There is no way to inject that handler into your other thread. You could spawn another thread to run Shutdown, but this still doesn't solve the problem of Initialize not being finished yet - it just moves the problem yet again. It does let you display a waiting dialog for the user.

Ultimately if this line of code hangs for a while:

_phoneListener.Initialize( _strAddress );

then there's nothing you can really do apart from wait for that to finish (either on the UI thread or in another thread with some form of waiting UI) and have a check after that call that causes the handler thread to exit.

_phoneListener.Initialize( _strAddress );
if (_exit.WaitOne(0))
{
    return;
}

In your main shutdown routine you want something like:

_exit.Set();
_th.Join();

// Rest of cleanup code

This will then wait for the thread to exit. I wouldn't recommend calling Abort on a thread ever, as you have no idea what will happen to any state that thread was referencing. If you have to Abort a thread then plan on restarting the whole application to be safe.

Simon Steele
Thanks, Simon. That explains why what I tried doesn't work. However, I feel like everyone responding is answering the wrong question.Let me try again - forget the call to Initialize(). When I start the API handling code in a thread, I can't stop it. The call to _listener.Shutdown() hangs, making me think the thread used to start the interface is still running, waiting for events from the API. How can I stop the thread? It has no loops, can check no flag, has no place to call WaitOne(). I need a way to signal the thread to shut itself down.Ideas?
DaveN59
BTW, the call to Initialize will eventually return, but it may be a matter of minutes rather than seconds. It's OK, though, as the rest of the program can run without the listener if necessary.
DaveN59
Unfortunately, without access to the code of the listener it's very hard to say why that is. The first thing I'd do is pause the debugger while hung in Shutdown(). Now have a look at what it's trying to do and what threads are active.Also, add a trace at the end of your thread function to find out if it's actually getting to the end and exiting. You can prove your thread is not around any more by calling Join() on it. If Join() returns your thread is not running any more.
Simon Steele
OK, after further investigation I found I have been chasing the wrong problem. All these great answers helped me realize the thread is NOT still running. The problem is, if I Initialize() the API on a separate thread, the call to Shutdown() always hangs -- essentially forever. This seems like a bug in the API code, so I'm up against a different wall now.Thanks to all who responded. I'll figure out who has the best answer to the original question and award the points there...
DaveN59
A: 

With the help of a fellow developer here, I was able to get the code working as desired. What I needed to do was take everything out of the thread except the call to Initialize(), so that the API instance was not running on a separate thread. I then had a different problem, as the status was set from 2 different threads and was not what I expected it to be when I went to clean up.

Here is the final solution:

// declare a background worker to init the API
private BackgroundWorker _bgWorker;

// ctor
public HiPathScreenPop( ... )
{
    ...

    _phoneListener = new ScreenPopTelephoneListener();
    _apiState = ApiState.Allocated;

    _phoneListener.StatusChanged += new _IScreenPopTelephoneListenerEvents_StatusChangedEventHandler( StatusChangedEvent );
    _phoneListener.ScreenPop += new _IScreenPopTelephoneListenerEvents_ScreenPopEventHandler( ScreenPopEvent );

    _bgWorker = new BackgroundWorker();
    _bgWorker.DoWork += new DoWorkEventHandler(StartInBackground);
    _bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bgWorker_RunWorkerCompleted );

    _bgWorker.RunWorkerAsync();
}

 void _bgWorker_DoWork( object sender, DoWorkEventArgs e )
{
    _phoneListener.Initialize( _strAddress );
}

void bgWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
    _apiState = ApiState.Initialized;  // probably not necessary...

    _phoneListener.StartListening( _intExtension.ToString() );
    _apiState = ApiState.Listening;
}

Now, the API instance is running in the same thread as the UI and everybody's happy. The call to Shutdwn() no longer hangs, I don't need Invoke() for the callbacks, and the long-running code executes in a background worker thread.

What this code does not show are the great ideas presented earlier for how to handle the case where the user shuts down before the call to Initialize() returns. The final code will be a synthesis of these solutions.

Thanks to all for the great feedback!

Dave

DaveN59