Home Products Download Events Support Registration

Home
Up

Create high performance of asynchronous pages in asp.net with SocketPro

A great and unusual way to boost your web application performance and scalability five times or more

UDAParts
support@udaparts.com
Updated on Nov. 3, 200
7

Source file: AsynWeb.zip for both C# and VB.NET

1.    Introduction

        Starting from version 2.0, ASP.NET fully supports asynchronous pages, which offer a neat solution to the problems caused by I/O-bound requests. You can see the two articles Asynchronous Pages in ASP.NET 2.0 and Scalable Apps with Asynchronous Programming in ASP.NET for details. The two articles are very interesting to us, and lead us to think how to integrate our SocketPro with ASP.NET 2 for developing high performance web application based on batching and asynchrony computation using multiple pools of pre-opened sockets and asynchronous request handlers.

2.   Brief introduction to socket pool

      A socket pool, similar to a thread pool, contains a number of sockets running within a few threads. Each of threads supports a few sockets. Socket pool manages required threads and contained sockets internally. The socket pool is created for supporting thousands of web/client requests from different machines. When an application starts, it pre-creates a pool of sockets that are connected to a remote host. Afterwards, we can bind an opened socket with one or more objects. Those objects use their bound sockets to process all of their requests. See the following Figure 1.

Figure 1: A socket pool contains six sockets hosted within three threads.

      Like a thread pool, we can use a limited number of sockets shared by different requests. When a client sends a request, we first lock a socket and its bound objects for simplicity in general, and send all the requests in one batch to a remote host. When a remote host processes all of them and returns their results back, we can unlock the socket and its bound objects, and release all of them as one unit back into the pool after handling return results. As you can see, we can avoid creating sockets and objects and building connections repeatedly so that an application performance and scalability are significantly improved using a small amount of pre-allocated resource.

    Socket pool offers two levels of parallelism. First of all, one thread is able to support multiple non-blocking socket communications. This is called as non-blocking socket communication parallelism. Take the above figure as an example, each of three threads support two non-blocking socket communications in parallel. Sometimes, client side processing may be CPU intensive, and requires taking advantage of multiple CPUs through multithreading, and handle returning results within our web or middle tier application in parallel. Our socket pool does offer this feature too. This is the second level parallelism for socket pool through multithreading. 

3.   Incredibly easy and simple to integrate SocketPro with ASP.Net 2.0 asynchronous pages

      Sample source codes for both vb.net and C# are available at ..\samples\AsyWeb. SocketPro is written from asynchrony computation that enables us to integrate various objects with asp.net asynchronous pages very easily. Let us see how simple code can be.

A.  Start one or multiple pools of sockets and bind objects one time only.

    public class Global : System.Web.HttpApplication
    {
        //the pool for this sample AsyncADO service
        public static CSocketPool<CMyAdoHandler> m_AsyncADO;

        //the pool for UDAParts generic remote database service using MS OLEDB technology
        public static CSocketPool<CAsynDBLite> m_AsyncRdb;

        [MTAThread]
        protected void Application_Start(object sender, EventArgs e)
        {
            //your own ADO.NET based service
            m_AsyncADO = new CSocketPool<CMyAdoHandler>();

            //UDAParts remote database service using OLEDB technology
            m_AsyncRdb = new CSocketPool<CAsynDBLite>();

            //error treatment ignored here
            bool bSuc = m_AsyncRdb.StartSocketPool("localhost", 20905, "SocketPro", "PassOne", 3);
            bSuc = m_AsyncADO.StartSocketPool("Localhost", 20905, "YourUserID", "YourPassword", 1, 0, tagEncryptionMethod.NoEncryption, false);
        }

        protected void Application_End(object sender, EventArgs e)
        {
            if (m_AsyncRdb != null)
            {
                m_AsyncRdb.ShutdownPool();
                m_AsyncRdb.Dispose();
            }
            if (m_AsyncADO != null)
            {
                m_AsyncADO.ShutdownPool();
                m_AsyncADO.Dispose();
            }
        }
    }

      When a web application starts, it initially creates two socket pools in the above and connects all of associated services to remote servers. This just happens one time only. Afterwards, we reuse them without repeatedly building connections. When the web application stops, we shuts down the two pools of sockets and destroys all of their associated asynchronous handlers.

      B.   Send all of requests in one batch asynchronously.

    public partial class _Default : System.Web.UI.Page
    {
        CMyAdoHandler m_AsynAdoHandler;
        protected void btnExecute_Click(object sender, EventArgs e)
        {
            //Register two event handlers
            AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginAsyncOperation), new EndEventHandler(EndAsyncOperation));
        }

        private IAsyncResult BeginAsyncOperation(object sender, EventArgs e, AsyncCallback cb, object state)
        {
            m_AsynAdoHandler = Global.m_AsyncADO.Lock(100); //timeout -- 0.1 second
            if (m_AsynAdoHandler != null)
            {
                //Start batch
                bool bSuc = m_AsynAdoHandler.GetAttchedClientSocket().BeginBatching();
                
                //delete some records
                m_AsynAdoHandler.ExecuteNoQueryAsyn("Delete from Shippers Where ShipperID > 3");

                string strSQL = "Insert into Shippers (CompanyName, Phone) Values ('";
                strSQL += txtCompany.Text;
                strSQL += "', '";
                strSQL += txtPhoneNumber.Text;
                strSQL += "')";

                //Insert a record
                m_AsynAdoHandler.ExecuteNoQueryAsyn(strSQL);

                //Query records and fetch all of them
                m_AsynAdoHandler.GetDataTableAsyn("Select * from Shippers");

                //remember a callback
                m_AsynAdoHandler.GetAttchedClientSocket().Callback = cb;

                //Batch requests and remember a callback cb
                m_AsynAdoHandler.GetAttchedClientSocket().Commit(true);

                return m_AsynAdoHandler.GetAttchedClientSocket();
            }
            else //no socket is available
            {
                //report the error that web server is very busy here
            }
            return null;
        }
    }

      At the beginning, the code locks an ADO.NET database handler. Once getting the handler, we batch all requests, remember a callback, and send these requests to a remote SocketPro server for processing without waiting for results.

      C.  Bind a table with web DataGridView control when all of requests are processed and returned.

	private void EndAsyncOperation(IAsyncResult ar)
        {
            if (m_AsynDBLite != null)
            {
                //binding the last table onto a grid view
                DataTable dt = m_AsynAdoHandler.m_GetDataTableRtn;
                gvRowset.DataSource = dt;
                gvRowset.DataBind();

                //unlock the attached socket, and return it back into socket pool for reuse
                Global.m_AsyncADO.Unlock(m_AsynAdoHandler);
            }
        }

        At the end, once all of requests are processed and a data table is bound with a web data grid view control, we unlock the socket and its associated database handler first, and return them back into pool for reuse at end.

        D.    Process multiple requests in parallel with waiting.

        In addition to process requests in batch using one socket, we can also process multiples requests in parallel using multiple socket connections simultaneously. See the below code.

    public partial class Parallel : System.Web.UI.Page
    {
        protected void btnParallel_Click(object sender, EventArgs e)
        {
            //lock two instances of data accessing handlers, CAsynDBLite and CMyAdoHandler
            CAsynDBLite AsynDBLiteHandler = Global.m_AsyncRdb.Lock(100);
            CMyAdoHandler AsyncAdoHandler = Global.m_AsyncADO.Lock(100);

            if (AsynDBLiteHandler == null || AsyncAdoHandler == null)
            {
                //indicate error message like server is too busy
                return;
            }

            if (!AsynDBLiteHandler.DBConnected)
            {
                //connect to DB one time only
                AsynDBLiteHandler.ConnectDB(null); //using global oledb connection string at server side
                AsynDBLiteHandler.GetAttchedClientSocket().WaitAll();
            }

            //execute two SQLs in parallel with two different instances, AsynDBLiteHandler and AsyncAdoHandler
            AsynDBLiteHandler.OpenRowset(txtSQL1.Text, "SQL1");
            AsyncAdoHandler.GetDataTableAsyn(txtSQL2.Text);

            //Block until two requests are processed
            AsyncAdoHandler.GetAttchedClientSocket().WaitAll();
            AsynDBLiteHandler.GetAttchedClientSocket().WaitAll();

            gvQueryOne.DataSource = AsynDBLiteHandler.CurrentDataTable;
            gvQueryTwo.DataSource = AsyncAdoHandler.m_GetDataTableRtn;

            gvQueryOne.DataBind();
            gvQueryTwo.DataBind();

            //unlock sockets and their associated DB handlers, and return them back into pool for reuse
            Global.m_AsyncRdb.Unlock(AsynDBLiteHandler);
            Global.m_AsyncADO.Unlock(AsyncAdoHandler);
        }
    }

        By this way, you can divide a large request into a set of small requests and process them in parallel by use of multiple socket connections at the same time, which may lead to better performance and scalability.

    E.     Process multiple requests in parallel without waiting

    In addition, we could use the method RegisterAsyncTask to register multiple tasks and let them all run asynchronously in parallel, as shown below.

    public partial class ParallelPage : System.Web.UI.Page
    {
        CAsynDBLite m_dbCmd;
        CMyAdoHandler m_dbQuery;
        int m_nFirst;

	//start two tasks for parallel computation
        protected void idDoMultiTask_Click(object sender, EventArgs e)
        {
            m_nFirst = 0;
            PageAsyncTask task = new PageAsyncTask(BeginAsyncOperationQuery, EndAsyncOperationQuery, TimeoutAsyncOperationQuery, null, true);
            RegisterAsyncTask(task);

            task = new PageAsyncTask(BeginAsyncOperationCmd, EndAsyncOperationCmd, TimeoutAsyncOperationCmd, null, true);
            RegisterAsyncTask(task);
        }

	IAsyncResult BeginAsyncOperationCmd(object sender, EventArgs e, AsyncCallback cb, object state)
        {
            m_dbCmd = Global.m_AsyncRdb.Lock();
            if (m_dbCmd != null)
            {
                CClientSocket cs = m_dbCmd.GetAttchedClientSocket();
                cs.Callback = cb; //remember a callback
                if (!m_dbCmd.DBConnected)
                {
                    m_dbCmd.ConnectDB(null); //use global oledb connection string at server side
                }
                m_dbCmd.ExecuteNonQuery(txtCmd.Text);
                return cs;
            }
            return null;
        }

        void EndAsyncOperationCmd(IAsyncResult ar)
        {
            if (m_dbCmd != null)
            {
                if (m_nFirst == 0)
                {
                    m_nFirst = 1;
                    lblInfo.Text = "Command First";
                }
                Global.m_AsyncRdb.Unlock(m_dbCmd);
            }
        }

        void TimeoutAsyncOperationCmd(IAsyncResult ar)
        {
            lblInfo.Text = "Data temporarily unavailable";
        }

        IAsyncResult BeginAsyncOperationQuery(object sender, EventArgs e, AsyncCallback cb, object state)
        {
            m_dbQuery = Global.m_AsyncADO.Lock();
            if (m_dbQuery != null)
            {
                CClientSocket cs = m_dbQuery.GetAttchedClientSocket();
                cs.Callback = cb; //remember callback;
                m_dbQuery.GetDataTableAsyn(txtQuery.Text);
                return cs;
            }
            return null;
        }

        void EndAsyncOperationQuery(IAsyncResult ar)
        {
            if (m_dbQuery != null)
            {
                DataTable dt = m_dbQuery.m_GetDataTableRtn;
                gvQuery.DataSource = dt;
                gvQuery.DataBind();
                Global.m_AsyncADO.Unlock(m_dbQuery);
                if (m_nFirst == 0)
                {
                    m_nFirst = 2;
                    lblInfo.Text = "Query First";
                }
            }
        }

        void TimeoutAsyncOperationQuery(IAsyncResult ar)
        {
            lblInfo.Text = "Data temporarily unavailable";
        }
    }

      SocketPro is very powerful and has a lot of features. As a sample, the code also has a few delegates to monitor dataset generation when we fetch one or multiple rowsets. For all of other codes, you can see the source file, debug it and see code execution. After understanding how the code works, you can add more and more features (For example, monitoring various socket events and data movement, canceling fetching records, notifying messages, online compressing, securing data movement , sending requests when fetching records, …… etc) according to your requirements.

4.    CClientSocket implements the interface IAsyncResult

    It is pointed that the class CClientSocket implements the .NET interface IAsyncResult. Like our remote database service, all of your services can be used within a web or middle tier application like this sample. As you can see, our SocketPro supports dotNet asynchronous pattern/pages very well.

5.   FAQs:

      I listed a number of frequent asked questions about this article so that it further eliminates some your concerns.

      Does the remote database service is free? Can I get source code for it?

You can use udb.dll and uodbsvr.dll through SocketPro for free. Beginning with SocketPro version 4.4.0.2, UDAParts makes remote database service completely free to any one for any purpose as long as you do not do anything against or harmful to UDAParts. In regards to source file, you can see the source file for udb.dll inside SocketPro package. However, you can’t see the source file for uodbsvr.dll that is written from our OleDBPro library. Because our OleDBPro and its source code are not free to the public, we will not open its source code to the public but to our customers only.

How fast and scalable with remote database service? Do you have any comparison with dotNet remoting in performance and scalability?

We have compared our SocketPro with dotNet remoting as described in the article Develop high performance distributed applications with batching, asynchrony, and parallel computation -- Performance comparison between SocketPro and dotNet remoting. You can see how fast and scalable SocketPro is. In short, dotNet remoting can never match SocketPro in performance and scalability under any cases no matter how much time and effort you put on dotNet remoting. SocketPro is carefully written directly from window socket APIs using ATL/COM that makes sure it delivers super performance and scalability for all of development languages with special features, batching, asynchrony and parallel computation with online compressing, although it has COM interop cost between native code and managed code.

      As you may know that MS SQL server provides notification service. Can I use the sample code to notify other clients when I change a table?

      Yes, you can! SocketPro has a built-in service for every socket connection. After a set of requests are processed and returned in the function void EndAsyncOperation(IAsyncResult ar), you can call the below code to notify all of clients that joined group 1.

      m_DBHandler.Socket.Speak("All guys, I just updated the table Orders where OrderID = 10250. Please update your GUI controls with latest data!", 1);

      For details, see the article Notify your coworkers any messages anywhere.