Geeks With Blogs

@moitoius
  • moitoius Warcraft II on my HTC Wizard (still waiting for the TyTN II), Apple eat your heart out... about 2208 days ago
  • moitoius getting my tytn II hopefully today! hold thumbs! about 2209 days ago
  • moitoius going to hillsong on the 22nd! yeah! about 2210 days ago
  • moitoius nearly home, ready for the weekend and ttf fun. about 2217 days ago
  • moitoius also trying my hand at ROAM landscapes. with geomorphs. in xna. hell yeah. about 2217 days ago
  • moitoius is very impressed with the twitter support in fring. about 2217 days ago

News This blog has moved to http://jonathan.dickinsons.co.za/blog.
Jonathan Dickinson Moved to: jonathan.dickinsons.co.za/blog

By now you should all know that you should never use long-running code in a WebService, as ASP.Net will simply run out of worker threads.

However, another challenge remains, how do we allow clients to receive events without having to resort to polling (which I think is a really BAD practice).

I read about how one could do this over at Udi Dahan's blog, but this seems like a bad practice to me, most importantly - it uses polling. Most people seem to forget that bandwidth can get expensive these days.

I further appropriated his method and used Asyncronous HTTP handlers to allow the client to resume when the request was done. I am not going to explain the code, as it is short and sweet.

Testing

If you want to test the code you can use the browser, if you can't figure out how to use it maybe you should find another hobby/job ;).

AsyncWebServiceResult.cs

#region Using Statements

using System;
using System.Collections.Generic;
using System.Threading;
using System.Web;


#endregion Using Statements

namespace WebService1
{
    public class AsyncWebServiceResult : IAsyncResult
    {
        #region Fields

        private static Dictionary<string, AsyncWebServiceResult> _workers = new Dictionary<string, AsyncWebServiceResult>();
        private static ReaderWriterLockSlim _workersLock = new ReaderWriterLockSlim();

        private AsyncCallback _callback;
        private bool _completed;
        private HttpContext _context;
        private Exception _ex;
        private string _id;
        private Func<Object> _method;
        private Object _return;
        private bool _started;
        private object _state;

        #endregion Fields

        #region Constructors

        public AsyncWebServiceResult(Func<Object> worker, string id)
        {
            _method = worker;
            _id = id;
        } //  AsyncWebServiceResult

        #endregion Constructors

        #region Properties

        public object AsyncState
        {
            get { return _state; }
        } // object AsyncState

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get { return null; }
        } // System.Threading.WaitHandle AsyncWaitHandle

        public bool CompletedSynchronously
        {
            get { return false; }
        } // bool CompletedSynchronously

        public bool IsCompleted
        {
            get { return _completed; }
        } // bool IsCompleted

        #endregion Properties

        #region Methods

        public static string Create(Func<Object> worker)
        {
            try
            {
                _workersLock.EnterWriteLock();

                string id = Guid.NewGuid().ToString();

                AsyncWebServiceResult res = new AsyncWebServiceResult(worker, id);

                _workers.Add(id, res);

                return id;
            }
            finally
            {
                _workersLock.ExitWriteLock();
            }
        } // string Create

        public static Object End(string id)
        {
            try
            {
                _workersLock.EnterWriteLock();

                AsyncWebServiceResult val;
                if (!_workers.TryGetValue(id, out val))
                    throw new InvalidOperationException();

                if (!val.IsCompleted)
                    throw new InvalidOperationException();

                _workers.Remove(id);

                if (val._ex != null)
                    throw val._ex;

                return val._return;
            }
            finally
            {
                _workersLock.ExitWriteLock();
            }
        } // Object End

        internal static IAsyncResult Start(string id, AsyncCallback callback, HttpContext context, object extraData)
        {
            try
            {
                _workersLock.EnterReadLock();

                AsyncWebServiceResult val;
                if (!_workers.TryGetValue(id, out val))
                    throw new InvalidOperationException();

                val.StartWorker(id, callback, context, extraData);
                return val;
            }
            finally
            {
                _workersLock.ExitReadLock();
            }
        } // IAsyncResult Start

        private void Complete()
        {
            _completed = true;
            if (_callback != null)
                _callback(this);
        } // void Complete

        private void StartWorker(string id, AsyncCallback callback, HttpContext context, object state)
        {
            if (_started)
                return;

            _started = true;
            _callback = callback;
            _state = state;
            _context = context;
            ThreadPool.QueueUserWorkItem(new WaitCallback(StartWorker), null);
        } // void StartWorker

        private void StartWorker(object state)
        {
            try
            {
                _return = _method();
            }
            catch(Exception e)
            {
                _ex = e;
            }
            Complete();
        } // void StartWorker

        #endregion Methods
    } // Class AsyncWebServiceResult
}

wswait.ashx

#region Using Statements

using System;
using System.Web;


#endregion Using Statements

namespace WebService1
{
    public class wswait : IHttpAsyncHandler
    {
        #region Properties

        public bool IsReusable
        {
            get { return false; }
        } // bool IsReusable

        #endregion Properties

        #region Methods

        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
        {
            string id = context.Request.Params["id"];
            if (id == null)
                throw new InvalidOperationException();

            return AsyncWebServiceResult.Start(id, cb, context, extraData);
        } // IAsyncResult BeginProcessRequest

        public void EndProcessRequest(IAsyncResult result)
        {
        }

        public void ProcessRequest(HttpContext context)
        {
            throw new InvalidOperationException();
        } // void ProcessRequest

        #endregion Methods
    } // Class wswait
}

LongService.asmx

#region Using Statements

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;

#endregion Using Statements

namespace WebService1
{
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
    // [System.Web.Script.Services.ScriptService]
    /// 
    /// Summary description for LongService
    /// 
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    public class LongService : System.Web.Services.WebService
    {
        #region Methods

        [WebMethod]
        public string HelloWorldEnd(string id)
        {
            return (string)AsyncWebServiceResult.End(id);
        } // string HelloWorldEnd

        [WebMethod]
        public string HelloWorldStart(string val)
        {
            return string.Format("wswait.ashx?id={0}", AsyncWebServiceResult.Create(() => Work(val)));
        } // string HelloWorldStart

        private object Work(string val)
        {
            Thread.Sleep(5000);
            return "Hello " + val;
        } // object Work

        #endregion Methods
    } // Class LongService
}
Posted on Wednesday, September 10, 2008 1:32 AM C# , Patterns and Practices , Tips | Back to top

Copyright © Jonathan Dickinson | Powered by: GeeksWithBlogs.net | Join free