views:

114

answers:

1

Hi I have a producer - consumer pattern. The WPF UI is sort of ancilliary to a long-running worker thread which is listening to packets and enqueing tasks. In practise all the messages are dequeued and then the UI consumer processes.

The issue I have is that I have a UIcontroller class which is responsible for all WPF GUI components. It opens a new window displaying each dequeued task and holds a thread-safe collection of referenced windows.

I signal by Despatcher.BeginInvoke. No surprises there.

The issue I have is that I want to suspend/literally halt my worker thread if the UIController class successfully opens a cconfigured number of windows. There is a limit on the number of windows open. Once the number of windows open reduces back within bounds I resume the processing of the queue. I tried holding my window collection here in this class and passing it to the UI via the call where it is updated by reference but the Begin Invoke is Asynchronous and the count is not updated in time in the loop of the worker thread.

I could test the no of windows open in the UIcontroller class, ignore and even requeue the task from there - essentially taking no action. But I'd like a cleaner solution.

Can I do some sort of clean callback?

    public void Work()
    {

        while (true)
        {

            Log.Instance.Info("****In worker THREAD " + _worker.Name);

            while (_controller.IncomingMessages.Count > 0 && [--test for no of windows open on GU thread somehow - I did try holding a reference to the collection in this class--])
            {
                try
                {
                    Log.Instance.Info("****In Notification worker THREAD and messages to process " + _worker.Name);

                    Messages.AlertMessage task = null;
                    lock (_locker)
                    {

                        if (_controller.IncomingMessages.Count > 0 && ToasterPopUps.Count < 5)
                        {
                            task = _controller.IncomingMessages.Dequeue();
                            if (task == null)
                            {
                                return;
                            }
                        }

                        if (task != null)
                        {
                            Log.Instance.Info("Dequeing: " + task + " " + task.ID + " from Notification thread");

                _UIthread.BeginInvoke(DispatcherPriority.Background, new JoinUIThread(DespatchUIThread), task, _UIthread);
                        }
                    }

                }
                catch (Exception err)
                {
                    Log.Instance.Critical(string.Format("Unexpected Error in PollPopUp Thread Queue {0} ", err));
                }

            }

            Log.Instance.Info("No more Notification tasks - wait for a signal");

            _wh.WaitOne();

        }

    }

    public void DespatchUIThread(Messages.AlertMessage task, System.Windows.Threading.Dispatcher dispatcherThread)
    {
        try
        {
            _controller.CreateWindow(task, dispatcherThread);
        }
        catch (Exception err)
        {
            Log.Instance.Critical("Critical error " + err);
        }
    }
A: 

If I understand you, you want the Producer thread to wait while the number of UI windows is at a certain threshold. I would use a Semaphore shared by the producer and consumer and initialized to the max window count to accomplish this behavior.

const int MaxWindowCount = 5;
Sempahore semaphore = new Semaphore(0, MaxWindowCount );

Before each enqueue, you call

semaphore.WaitOne();

And once each task is accomplished and its window is closed, call

semaphore.Release();

Once MaxWindowCount items have been enqueued, your producer will wait on its next call to WaitOne() and will wait until Release() is called.

Hmm, this may not accomplish what you want, since it is limiting the number of items in the queue to MaxWindowCount , not necessarily the number of open windows. It assumes that each item in the enqueued has an open window, which may not be true in your case.

If that's an invalid assumption, you could have a thread responsible for pulling items from the queue. This thread would be responsible for calling WaitOne() while handing items off to the UIController. The UIController is still responsible for calling Release(). In effect, you would have two producer-consumer queues. One unbounded queue for tasks and one bounded queue for windows.

--Added May 21, 2010 --

Another option might be to have the call to CreateWindow take a callback parameter, telling the controller the number of open windows.

Using the following delegate as a parameter to CreateWindow, you could keep the count of open windows in the Worker and act accordingly when the max is reached.

public delegate void CreateWindowCallback( int numOpenWindows );

You'd may keep a count of open windows in your worker and you'd need to let worker know the max window count. Alternatively, you could keep a CanOpenNewWindows boolean in your Controller class.

Before a call to _UIthread.BeginInvoke the worker could check the current window count (or the CanOpenNewWindows property) and if it shouldn't open a new window call WaitOne(). You would still need the something to know when windows are closed, in order to Release/Signal the worker to continue.

In the case where window creation fails, or the controller doesn't need to display a window you can let the worker know that the open window count hasn't increased. This does assume that _controller knows whether or not a window was created.

-- Another thought --

It may be possible to keep all of the window creation logic in your UIController. UIController would need to know the UIThread and modify the CreateWindow call to accept only a task.

A call to CreateWindow(task) would call BeginInvoke if there are less than max windows open. Otherwise, it calls WaitOne() and waits for one of the windows to signal that they've closed. We could use a semaphore here, calling WaitOne() after each successful window creation. Each window would be responsible for calling Release() when it closes.

This way, when the worker calls CreateWindow directly, it will block when max window count is reached and it never has to know the max window count or much else about the UI.

Ed Gonzalez
Thanks for the input. The issue is that the consumer may be unsuccessful in opening the window, or, decide not to open one.The last suggestion sounds good. I'm not clear exactly how this would work but I will have a play around.
Mike
Whilst I work out the bounded and unbounded solution I have had some success with just (re) enqueing the unwanted message back on the queue. It's a tad messy as I am having to pass around a fair number of classes in the constructor so that the UIcontroller can see my queue. What ever I do, I need to refactor.
Mike
My (surprisingly straight-forward, as it happens) solution is to (optimistically) open up to max no of windows by calling the UI controller. Then I WaitOne. The UIcontroller re-activates the worker thread with a count reset to the no of windows available: whenever one or more windows are closed...The only issue is that if one or more windows fails to open, I may only open 1 window rather than the maximum allowed. If all fail to open, I may block 'permanently'. I can deal with this in error handling.
Mike
I realize I'm spewing ideas out. The more I think about it, the more ways there seems to be to skin this cat.
Ed Gonzalez
Ed - many thanks.In the end I did precisely what you suggested- use a delegate. I hadn't realised though you could put the delegate as a parameter - I need to re-read the use of delegates!
Mike