Click here to Skip to main content
15,891,136 members
Articles / Programming Languages / C#
Article

Create in-process asynchronous services in C#

Rate me:
Please Sign up or sign in to vote.
4.73/5 (27 votes)
6 Feb 20064 min read 129.5K   1.5K   138   22
Use C# along with delegates, threads, and message queueing to create powerful in-process asynchronous services.

Sample Image

Introduction

This article describes how to make an in-process asynchronous service in your C# applications and why you would want to. The classes for the same provide what is often referred to as background processing. However, they also provide a foundation for building stable and robust user interfaces centered around message passing (like Windows itself) and state machines. The InprocessAsynchronousService class provided in the sample project is a base class that you can inherit your own classes from to build custom services. The sample project shows how to create these custom services.

Background

There are many instances where synchronous processing has negative side effects. Over the years, I have moved more and more away from synchronous programming to asynchronous programming. At first, it is easier to write synchronous code solutions. However, if you force yourself to start using asynchronous multithreaded programming all the way through a project's life cycle, you will come to appreciate the benefits that come later on as your project becomes larger and larger. The growth of an application usually introduces bottlenecks, bugs, artificial barriers, and unnecessary dependencies caused by synchronous programming. Some of the situations where you might want to operate in a multithreaded way include: updating a progress bar in the middle of a long running process, saving data to a database over the Internet while allowing the user to continue working with the UI, using a state machine to process requests from a UI thread instead of putting code behind click events or selected item changed events, uploading a video file while another one is being recorded, or polling a message queue like MSMQ or MQSeries while allowing the user to continue working in the UI.

Using the code

The following code shows the InprocessAsynchronousService base class. The main features of this class include:

  • A thread synchronized way to drop messages into its queue.
  • Supports Start, Stop, and Pause functions.
  • Has a throttle control mechanism to 'prioritize' the aggressive use of processor cycles by increasing or decreasing the Thread.Sleep time in the MainLoop.
  • Uses a ManualResetEvent to pause/unpause a thread in an efficient manner
  • Provides events to alert listeners to any change in throttle speed or run state (start, stop, pause).
  • Uses the method attribute [MethodImpl(MethodImplOptions.Synchronized)] to force threads calling into this class to take turns accessing a particular method.
C#
using System;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Collections;

namespace WindowsApplication1
{
    public delegate void PulseHandler(object args);
    public delegate void SpeedChangeHandler(int milliseconds);
    public delegate void RunStateChangeHandler(RunState state);
    public enum RunSpeed{Fast=1,Faster=2, Fastest=3, 
                         Slow=4,Slower=5,Slowest=6};
    public enum RunState{Started=1,Stoped=2,Paused=3};

    /// <summary>
    /// Summary description for InprocessAsynchronousService.
    /// </summary>
    public class InprocessAsynchronousService
    {
        //Used to put the thread into a paused 
        //state and to unpause that thread when
        //the Start method is called 
        //The thread is paused when the Pause 
        //method is called or temporarily paused
        //as part of the throttle mechanism
        //Suggested by yfoulon 
        //http://www.codeproject.com/csharp/
        //   InprocessAsynServicesInCS.asp#xx1276684xx
        private ManualResetEvent PauseTrigger = 
                       new ManualResetEvent(false);

        private Queue q = null;
        private Queue synchQ = null;

        //Delegate used to signal that it is time to process
        protected PulseHandler Heartbeat = null;

        //Used to adjust run speed of this threaded service
        protected Throttle SpeedControl = null;

        //Event that is fired off whenever 
        //the throttle speed gets changed
        public event SpeedChangeHandler SpeedChanged;
        //Event that is fired off 
        //whenever the runstate gets changed
        public event RunStateChangeHandler RunStateChanged;

        

        //Allows us to remember what run state we are in
        private int runstate = (int)RunState.Stoped;

        
        public InprocessAsynchronousService()
        {
            //Create the queue to hold messages
            q = new Queue();
            //Get a synchronized version so we can 
            //do multithreaded access to the queue
            synchQ = Queue.Synchronized(q);
        }

        #region Messaging
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void SendMessage(ServiceMessage Message)
        {
            synchQ.Enqueue(Message);            
        }
        #endregion

        #region Public throttle control
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void SetSpeed(RunSpeed speed)
        {
            switch(speed)
            {
                case RunSpeed.Fast:
                    SpeedControl.Fast();
                    break;

                case RunSpeed.Faster:
                    SpeedControl.Faster();
                    break;

                case RunSpeed.Fastest:
                    SpeedControl.Fastest();
                    break;

                case RunSpeed.Slow:
                    SpeedControl.Slow();
                    break;

                case RunSpeed.Slower:
                    SpeedControl.Slower();
                    break;

                case RunSpeed.Slowest:
                    SpeedControl.Slowest();
                    break;
            }

            
            //Let listeners know we just changed the speed
            if(SpeedChanged != null)
            {
                SpeedChanged(SpeedControl.Speed);
            }
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void SetSpeed(int milliseconds)
        {
            SpeedControl.GoThisFast(milliseconds);

            

            //Let listeners know we just changed the speed
            if(SpeedChanged != null)
            {
                SpeedChanged(SpeedControl.Speed);
            }
        }
        #endregion

        #region Run Control
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Start()
        {
            if(runstate != (int)RunState.Started)
            {
                if(runstate == (int)RunState.Paused)
                {
                    //Set State
                    runstate = (int)RunState.Started;

                    //Unpause the thread
                    PauseTrigger.Set();

                    //Let listeners know the state changed
                    if(RunStateChanged != null)
                    {
                        RunStateChanged((RunState)runstate);
                    }
                }
                else if(runstate == (int)RunState.Stoped)
                {
                    //Set State
                    runstate = (int)RunState.Started;

                    //Let listeners know the state changed
                    if(RunStateChanged != null)
                    {
                        RunStateChanged((RunState)runstate);
                    }

                    //Delegate execution of this 
                    //thread to the MainLoop method
                    ThreadStart ts = new ThreadStart(MainLoop);
                    //Create the thread
                    Thread t = new Thread(ts);
                    //Begin
                    t.Start();
                }
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Stop()
        {
            //Kill the thread
            if(runstate != (int)RunState.Stoped)
            {
                //Set the throttle to stop
                SpeedControl.GoThisFast(0);

                //Put Stop message in the queue
                synchQ.Enqueue(new ServiceMessage(RunState.Stoped));

                //Set State
                runstate = (int)RunState.Stoped;

                //Let listeners know the state changed
                if(RunStateChanged != null)
                {
                    RunStateChanged((RunState)runstate);
                }
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Pause()
        {
            if(runstate != (int)RunState.Paused)
            {
                //Set state
                runstate = (int)RunState.Paused;

                //Let listeners know the state changed
                if(RunStateChanged != null)
                {
                    RunStateChanged((RunState)runstate);
                }
            }
        }
        #endregion

        #region Thread lives in a infinite loop 
           within this method until Stop is called
        private void MainLoop()
        {
            for(; ; )
            {
                if(runstate == (int)RunState.Paused)
                {
                    PauseTrigger.WaitOne(-1, false);
                    //Reset the trigger so 
                    //it can be triggered again
                    PauseTrigger.Reset();
                }

                //If there are no messages 
                //to pass on then just fire with a null
                if(q.Count == 0)
                {
                    //Fire!
                    Heartbeat.DynamicInvoke(new object[] { null });
                }
                else
                {
                    //Get the message from the queue
                    ServiceMessage msg = 
                       (ServiceMessage)synchQ.Dequeue();

                    //If we got a message then see 
                    //if it contains a run state change
                    if(msg.ChangeToRunState != 0)
                    {
                        //If so then see if this 
                        //is ONLY a run state change
                        if(msg.Args != null)
                        {
                            //If not then fire off the 
                            //heartbeat along with the args
                            Heartbeat.DynamicInvoke(new object[] { msg.Args });

                            //If the requested run state 
                            //change is to stop then return which kills
                            //this threads execution loop
                            if(((RunState)msg.ChangeToRunState) == 
                                               RunState.Stoped)
                            {
                                return;
                            }
                        }
                        else if(((RunState)msg.ChangeToRunState) 
                                             == RunState.Stoped)
                        {
                            //If the requested run state change 
                            //is to stop then return which kills
                            //this threads execution loop
                            return;
                        }
                    }
                    else
                    {
                        //If not then just pass on the args
                        Heartbeat.DynamicInvoke(new object[] { msg.Args });
                    }
                }


                //Pause the thread until the throttle period is over or until
                //the Start method is called
                PauseTrigger.WaitOne(SpeedControl.Speed, false);
            }
        }
        #endregion
    }
}

Since this is a generic base class, no real work should be done in the MainLoop method. Instead, your custom class should inherit from this class and set the Heartbeat delegate to a method in your custom class. Then, each time the MainLoop pulses the Heartbeat delegate, your custom method will be called where you can do all the real work you want your class to do on this service thread. For example...

C#
Heartbeat = new PulseHandler(OnHeartbeat);

with the handler looking like...

C#
private void OnHeartbeat(object args)
{
    if(args != null)
    {
        if(ChangeColor != null)
        {
            ChangeColor((System.Drawing.Color)args);
        }
    }

    if(Tick != null)
    {
        Tick(DateTime.Now);
    }
}

Here is the full MyService class code from the sample project. This class inherits from the InprocessAsynchronousService class and does the actual work to be done on each heartbeat.

C#
using System;

namespace WindowsApplication1
{

    public delegate void LapseHandler(DateTime time);
    public delegate void ColorChangeHandler(System.Drawing.Color c);

    /// <summary>
    /// Summary description for MyService.
    /// </summary>
    public class MyService : InprocessAsynchronousService
    {
        

        public MyService()
        {
            //Attach the parents Heartbeat delegate 
            //to my local method called OnHeartbeat
            Heartbeat = new PulseHandler(OnHeartbeat);
            //Setup throttle
            SpeedControl = new Throttle(2000,1000, 
                            500,5000,10000,15000);
        }

        public MyService(int Fast, int Faster, int Fastest, 
                                   int Slow, int Slower, int Slowest)
        {
            //Attach the parents Heartbeat delegate 
            //to my local method called OnHeartbeat
            Heartbeat = new PulseHandler(OnHeartbeat);
            //Setup throttle
            SpeedControl = new Throttle(Fast,Faster, 
                               Fastest,Slow,Slower,Slowest);
        }

        private void OnHeartbeat(object args)
        {
            //If a ServiceMessage was added to the message 
            //queue and args were provided then...
            if(args != null)
            {
                //If someone has wired up to my ChangeColor event...
                if(ChangeColor != null)
                {
                    //Then fire the event
                    ChangeColor((System.Drawing.Color)args);
                }
            }

            //Every time the Heartbeat pulses even 
            //if there was no message in the queue
            //fire off the Tick event with the current time
            if(Tick != null)
            {
                Tick(DateTime.Now);
            }
        }

        public event LapseHandler Tick;
        public event ColorChangeHandler ChangeColor;
    }
}

The messages that are passed into the service class are not just simple object wrappers. Instead, they hold two bits of information. One is the args that you want passed to the Heartbeat handler in your custom service class. The other is a RunState change. Right now, the only RunState change that the InprocessAsynchronousService class supports is RunState.Stoped. If this RunState is set then when the MainLoop in the InprocessAsynchronousService class processes a message from the queue, the MainLoop will kill its thread by returning from the MainLoop method.

With this code, you can write your own in-process asynchronous services that perform all kinds of background processing, asynchronous UI control, and even implement more complex constructs such as state machines.

To see this code in action, check out the sample project provided. It demonstrates all the abilities of the InprocessAsynchronousService class. Try running the project, clicking the 'play' button then the 'pause' button, and then send multiple messages into the queue by clicking the Background Color button several times. Then press the 'play' button again. What this will do is it will stack up several color change messages in the MyService queue and when you 'press' play again, the messages will be executed one by one making the background color of the form change with each Heartbeat.

Points of Interest

  • Multithreaded processing encapsulated in an easy to use base class.
  • Uses queuing to allow requests to be cached while the thread works on dispatching them.
  • Provides methods similar to the Windows Services in the Control Panel Admin Tools (Start, Stop, Pause).

History

  • January 2006 - Added support for ManualResetEvent so that pausing the MainLoop thread doesn't take up 100% processor cycles.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
I have been coding since 4th grade in elementary school back in 1981. I concentrate mainly on Microsoft technologies from the old MSDOS/MSBASIC to coding in VC++ then Visual Basic and now .Net. I try to stay on top of the trends coming from Microsoft such as the Millennium Project, Windows DNA, and my new favorite Windows clustering servers.

I have dabbled in making games in my initial VC++ days, but spent most of the past 10 years working on business apps in VB, C++, and the past 7 years in C#.

I eventually hope to get my own company supporting me so I can concentrate on my real dream of creating clusters of automated robots for use in various hazardous industries.

Comments and Discussions

 
GeneralSimple Explanation of Complex topic Pin
chandan02853-Oct-13 19:47
chandan02853-Oct-13 19:47 
GeneralSwitch from Secondary Thread to Main Thread from a class objecj. Pin
Eleggett21-Jan-10 11:05
Eleggett21-Jan-10 11:05 
GeneralRe: Switch from Secondary Thread to Main Thread from a class objecj. Pin
Member 84858535-Nov-12 6:54
Member 84858535-Nov-12 6:54 
QuestionGood article. Is there a .NET 2.0 or later version of doing this? Pin
Rohit Wason17-Feb-09 4:19
Rohit Wason17-Feb-09 4:19 
GeneralThanks Pin
Aman Bhullar2-Jan-09 1:26
Aman Bhullar2-Jan-09 1:26 
GeneralMessages are queued after the STOP is hit. Pin
VaibhavGaikwad31-Jul-08 20:14
VaibhavGaikwad31-Jul-08 20:14 
GeneralRe: Messages are queued after the STOP is hit. Pin
Gerald Gibson Jr1-Aug-08 3:22
Gerald Gibson Jr1-Aug-08 3:22 
GeneralTransactional Message Queue Pin
Wshmel13-Mar-06 21:28
Wshmel13-Mar-06 21:28 
GeneralRe: Transactional Message Queue Pin
Gerald Gibson Jr14-Mar-06 3:21
Gerald Gibson Jr14-Mar-06 3:21 
QuestionThread Confusion... Pin
Sluggish21-Jan-06 8:44
Sluggish21-Jan-06 8:44 
AnswerRe: Thread Confusion... Pin
Gerald Gibson Jr21-Jan-06 10:19
Gerald Gibson Jr21-Jan-06 10:19 
1) Is the MainLoop the only method running under a thread?

- No. There is another thread running which controls the Windows form. Whenever you start a Windows forms exe appliction a thread is created called the UI thread. This thread internally works alot like the thread in the MainLoop. The UI thread has its own queue that gets messages pumped into it from Windows. When you move your mouse over your form a bunch of mousemove messages are sent to the UI threads message queue.

If you look in the example code you will see this code in Form1.cs...

<br />
private void myservice_ChangeColor(Color c)<br />
{<br />
	ColorChanger cc = new ColorChanger(ChangeColor);<br />
	this.Invoke(cc, new object[]{c});<br />
}<br />


This is the event handler listening to the ChangeColor event on the MyService class. This event is fired when a message from the MainLoop thread is processed and the message contains a color value.

The MainLoop threads execution extends out from the MainLoop into the OnHeartbeat event handler in MyService then it travels out from there into the myservice_ChangeColor event handler shown above. Even though by time you get to the myservice_ChangeColor you are in the Form1.cs this is still the MainLoop thread executing that code.

Now a key point to remember here is that you are not supposed to try to update UI controls (objects) that are bound to the UI thread from code that is being executed from a nonUI thread. The reason is because the UI thread could be sending a message to that control at any time and you can cause a lock or some other bad result if the MainLoop thread directly touches a UI control at that same moment.

Since the MainLoop thread is a nonUI thread then it must use a different method to update the UI with the color change. To do this it passes a message onto the UI threads message queue just like Windows does when you move your mouse across your form. The last line of code in the event handler above is this ...

<br />
this.Invoke(cc, new object[]{c});<br />


Here "this" refers to the form object itself. Invoke enqueues a message into the UI thread queue. The message that Invoke is passing into the UI thread queue tells the UI thread to execute the method that a delegate points to. The delegate is "cc" which points to the method called ChangeColor. Once the MainLoop thread finishes with this line of code a message is waiting on the UI threads queue and the MainLoop thread retracts back into the for( ;; ) infinite loop back in the MainLoop method. There it just waits till it is time to look for another message on its queue.

At any time after the color change message is put onto the UI threads queue the UI thread can grab this message and process it. Now since the UI thread is processing this message instead of the MainLoop thread the ChangeColor method can safely ask the form (a UI thread bound control) to change its background color to match the color in the message sent from the MainLoop thread.

So what you have here are two threads each with their own message queues and the MainLoop thread is throwing messages onto the UI threads queue asking it to update the form.

2) ...

The main function of the MainLoop threaded class is to provide a new threaded message queue processor that can process messages to do anything you like... not just UI events. You could have a very large file such as a video say 500MB in size and you want to send it up to a server in 1/2 MB chunks. And you dont want to make the video recording part of your app to stop and wait till this is uploaded. So you would then create a InprocessAsynchronousService with its own thread so that in the background (at the speed you set via the throttle) it will be uploading those video file chunks while you continue to use the video recorder part of your app to record more video. You could go further and create five InprocessAsynchronousService objects each with their own threads and each uploading a different video file. And all the while the UI part of your app continues to respond and act normally.

-- modified at 16:42 Saturday 21st January, 2006
GeneralRe: Thread Confusion... Pin
Sluggish21-Jan-06 11:38
Sluggish21-Jan-06 11:38 
Generalarchitecture and optimization Pin
yfoulon8-Nov-05 20:01
yfoulon8-Nov-05 20:01 
GeneralRe: architecture and optimization Pin
Gerald Gibson Jr9-Nov-05 3:40
Gerald Gibson Jr9-Nov-05 3:40 
GeneralRe: architecture and optimization Pin
yfoulon9-Nov-05 4:08
yfoulon9-Nov-05 4:08 
GeneralRe: architecture and optimization Pin
Gerald Gibson Jr9-Nov-05 4:20
Gerald Gibson Jr9-Nov-05 4:20 
GeneralRe: architecture and optimization Pin
yfoulon9-Nov-05 4:35
yfoulon9-Nov-05 4:35 
GeneralRe: architecture and optimization Pin
Gerald Gibson Jr9-Nov-05 4:43
Gerald Gibson Jr9-Nov-05 4:43 
GeneralThrottle Extensions Pin
Greg Roberts8-Nov-05 13:36
Greg Roberts8-Nov-05 13:36 
GeneralRe: Throttle Extensions Pin
Gerald Gibson Jr8-Nov-05 15:11
Gerald Gibson Jr8-Nov-05 15:11 
GeneralExcellent Pin
Matt Stanton7-Nov-05 17:36
Matt Stanton7-Nov-05 17:36 
GeneralRe: Excellent Pin
Gerald Gibson Jr7-Nov-05 17:38
Gerald Gibson Jr7-Nov-05 17:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.