IVR Best Practice: Use Multithreading to Enhance Graphical User Experience
By Fouad Jennawi, Senior Software Engineer, Pronexus
In a previous article we explained how Pronexus VBVoice™ IVR software’s WorkerThread control improves the caller experience while waiting for a lengthy operation; e.g. database access, to finish without affecting other operations and callers of the IVR system. Multithreading could also be used to enhance the graphical user experience for, let’s say, a system administrator who is watching the IVR calls live or trying to dynamically modify one of the IVR parameters exposed through the application GUI (Graphical User Interface).
Example
In order to successfully do that, we’re going to show an example of two Windows® forms, IVRForm and GUIForm, where each is running on and handled by a different thread, and they are using the Windows event-driven mechanism to pass requests between the two forms.
In order for those two forms to communicate with each other, each one need to hold a reference to the other:
public partial class IVRForm : Form
{
private GUIForm m_guiForm = null;
}
and
public partial class GUIForm : Form
{
private IVRForm m_ivrForm = null;
}
Load the Forms
The first form we want to load is the IVRForm, which will end up on the main thread. When the IVRForm is loaded, we’re going to create a new thread that is going to load the GUIForm, here is how:
public partial class IVRForm : Form
{
public IVRForm()
{
InitializeComponent();
}
private void IVRForm_Load(object sender, EventArgs e)
{
Thread thr = new Thread(new ParameterizedThreadStart(GUIThreadProc));
thr.Start(this);
}
private static void GUIThreadProc(object o)
{
IVRForm ivrForm = o as IVRForm;
if (ivrForm == null)
return;
ivrForm.m_guiForm = new GUIForm(ivrForm); // note 1
// note 2
ivrForm.m_guiForm.ShowDialog();
}
At note 1, we create a new GUIForm passing to its constructor a reference to this IVRForm. This constructor is the only constructor we want to use in runtime for the GUIForm so we’re going to hide the default constructor by making it private:
public partial class GUIForm : Form
{
private GUIForm()
{
InitializeComponent();
}
private IVRForm m_ivrForm = null;
public GUIForm(IVRForm ivrForm)
{
InitializeComponent();
m_ivrForm = ivrForm;
}
}
At note 2, we’re going to add code to make the IVRForm subscribe to an event from the GUIForm, but let’s create the event first.
Create an Event
For simplicity, instead of creating an event for each operation the GUIForm might ask the IVRForm to perform, we are going to use a generic event. The arguments of this event include an ID of the operation, one of the following IDs:
public enum CustomOperations
{
StartSystem,
StopSystem,
CloseForm
}
to be executed by the event subscriber, so our first step is to define the event argument class which inherits from the default Windows event arguments class EventArgs:
public class CustomOperationArgs : EventArgs
{
public readonly CustomOperations Operation;
public CustomOperationArgs(CustomOperations Operation)
: base()
{
this.Operation = Operation;
}
}
The second step is to define in the GUIForm a delegate that represents functions to accept this arguments class and an event that is capable of firing such functions. Here is how:
public partial class GUIForm : Form
{
public delegate void CustomOperationEventHandler(CustomOperationArgs e);
public event CustomOperationEventHandler CustomOperationEvent;
}
The final step is to fire this event whenever needed passing with it a new instance of the argument class. We are going to do that in three places: a button to start the IVR system, a button to stop the IVR system and when the GUIForm closes. We will use an asynchronous event firing for the first two to keep the GUIForm responsive while these operations may take time, and this is done by calling BeginInvoke. However, we will call just Invoke in the form closing case to let the GUIForm block until the IVRForm completely closes:
public partial class GUIForm : Form
{
private void cmdStartSystem_Click(object sender, EventArgs e)
{
m_ivrForm.BeginInvoke(CustomOperationEvent, new object[] { new CustomOperationArgs(CustomOperations.StartSystem) });
}
private void cmdStopSystem_Click(object sender, EventArgs e)
{
m_ivrForm.BeginInvoke(CustomOperationEvent, new object[] { new CustomOperationArgs(CustomOperations.StopSystem) });
}
private void GUIForm_FormClosing(object sender, FormClosingEventArgs e)
{
m_ivrForm.Invoke(CustomOperationEvent, new object[] { new CustomOperationArgs(CustomOperations.CloseForm) });
}
}
Subscribe to the Event
In order for the IVRForm to do something when the CustomOperationEvent is raised, we need to create a member function of the IVRForm if the delegate type we defined above and combine it with the event.
The member function is a straight-forward function that depending on the operation required executes the suitable code:
public partial class IVRForm : Form
{
private void m_guiForm_CustomOperationEvent(CustomOperationArgs e)
{
switch (e.Operation)
{
case CustomOperations.CloseForm:
Close();
break;
case CustomOperations.StartSystem:
// Start the IVR system
break;
case CustomOperations.StopSystem:
// Stop the IVR system
break;
}
}
}
And combining is done just after creating the GUIForm in note 2 above by calling the add handler operator (+=) on the event using this line of code:
ivrForm.m_guiForm.CustomOperationEvent += new
GUIForm.CustomOperationEventHandler(ivrForm.m_guiForm_CustomOperationEvent);
Summary
This example demonstrated how to separate two forms by running each one on its own thread and how to pass three different kinds of operation requests between them synchronously and asynchronously.
IVR Best Practice: Use Multithreading to Enhance Graphical User Experience
Leave a Reply
i just can say that this is new for me, so thanks…
cheers,
sally.
Bookmarked your post to stumbleupon!…
Great post. I subscribed to your rss….