Geeks With Blogs

@RobertGray
  • RobertGray #GameOfThrones should adapt Top Gear's model of having 2 shorter seasons per year (6-7 eps per 1/2 season). The wait is too long. about 467 days ago
  • RobertGray "explorer ." in windows cmd prompt opens explorer at current dir. #TIL about 467 days ago
  • RobertGray @handyandy888 Again! motherfucking corvette took me out again on the opening lap. I was on pole again! about 469 days ago

Rob Gray

Scenario/History

There is a bug in our Premise Utility that causes streetid’s to be set to 0 whenever the an existing premise record is saved.  StreetId’s are integral to accurate location information when dispatching ambulances, so getting a fix done was high on the agenda.  The problem is that for whatever reason the bug cannot immediately be fixed in the Premise Utility.  I am instead creating a small app that will be notified when a premise record change an perform a reverse geo code on the supplied latitude and longitude to obtain a streetid and update the premise record.

 

What did I do?

The existing .NET Reverse GeoCode component inherits from System.Windows.Forms and contains an old VB OCX that performs the logic (we’re still in process of converting rather our large code base from VB6/C++ to .NET).  This component is designed to be called from a Windows Forms app, where the user enters the Latitude and Longitude and waits for a response, which is returned by an Event to the caller:

 

private void button1_Click(object sender, EventArgs e)

{

    var geo = new ReverseGeovalidator(100);

    geo.ReverseGeovalidationComplete += new ReverseGeovalidator.ReverseGeovalidationEventHandler(geo_ReverseGeovalidationComplete);

 

    var latitude = 62531667;

    var longitude = 26988333;

 

    geo.ReverseGeovalidate(latitude, longitude);           

}

 

void geo_ReverseGeovalidationComplete(object sender, ReverseGeovalidationEventArgs eventArgs)

{

    // Populate the form with the data received from eventArgs

}

 

My application needed to run unattented (Window Service), monitoring the IPC server for notifications that a Premise had changed.  This meant no windows form and no waiting around for a display to be populated. 

 

I needed my application to accept a geo-coordinate and process reverse geo-code synchronously, so I could associated the resultant streetid with the premise id of the changed premise and then commit to the database.

 

My original thinking was to use ManualResetEvents.WaitOne() to block the caller until the event handler had been called and I could call ManualResetEvents.Set() to continue the operation.

 

public class GeoCoder

{

    ManualResetEvent geoDone;

    ReverseGeovalidationEventArgs data;

 

    public GeoCoder()

    {

        geoDone = new ManualResetEvent(false);                      

    }

 

    public int ReverseGeoCode(int latitude, int longitude)

    {

        geoDone.Reset();

        var geo = new ReverseGeovalidator(1500);

        geo.ReverseGeovalidationComplete += new ReverseGeovalidator.ReverseGeovalidationEventHandler(geo_ReverseGeovalidationComplete);

 

        geo.ReverseGeovalidate(latitude, longitude);

        geoDone.WaitOne();

        if (data != null)

        {

            return data.StreetID;

        }

        return 0;

    }

 

    void geo_ReverseGeovalidationComplete(object sender, ReverseGeovalidationEventArgs eventArgs)

    {

        data = eventArgs;

        geoDone.Set();

    }

}

 

The problem I found was that my unit test would lockup.

 

(Oh yeah, here’s the test)

[TestMethod]

public void ReverseGeoCodeTest()

{                           

    var gc = new GeoCoder();

    var latitude = 62531667;

    var longitude = 26988333;

 

    var streetId = gc.ReverseGeoCode(latitude, longitude);

 

    Assert.AreEqual(275535, streetId);

}

 

 

After a little bit of pondering I realised that geoDone.WaitOne() is blocking on the current thread, which is the same thread the event handler is on.  Result? The event handler will never get called.

 

I needed to put the event handler a separate thread, so it would eventually get called?  But How?

 

After a bit of playing around, I decided (realised?) to put the entire lot on a worker thread, a la Command Pattern.  I created a test Geo Processor that is essentially a job queue that co-ordinates off the queue every 2 seconds and processes them, returning the results when done.

 

public class GeoProcessor

{

    public delegate void GeoCodeEventHandler(object sender, GeoCodeEventArgs e);

    private Queue<GeoCoordinate> workQueue;

    public event GeoCodeEventHandler GeoCodeEventCompleted;

 

    // Timer will pull Coordinates off the work queue and process them.

    System.Timers.Timer workTimer;

 

    public GeoProcessor()

    {

        workQueue = new Queue<GeoCoordinate>();

        workTimer = new System.Timers.Timer();

        workTimer.Elapsed +=new ElapsedEventHandler(workTimer_Elapsed);

        workTimer.Interval = 2000;

    }

 

    // Pulls items from the queue and processes.

    void  workTimer_Elapsed(object sender, ElapsedEventArgs e)

    {

        workTimer.Stop();

 

        if (workQueue.Count > 0)

        {

            var coord = workQueue.Dequeue();

           

            //

            // Do some processing here

            //

 

            // Raise event to notify subscriber that work is done.

            OnGeoCodeComplete(new GeoCodeEventArgs(275535));

            if (workQueue.Count > 0) workTimer.Start();

        }           

    }

 

    // Queues the coordinate up for processing in the work queue

    public void QueueGeo(int latitude, int longitude)

    {

        workQueue.Enqueue(new GeoCoordinate { Latitude = latitude, Longitude = longitude });

        workTimer.Start();

    }

 

    protected void OnGeoCodeComplete(GeoCodeEventArgs e)

    {

        if (GeoCodeEventCompleted != null)

        {

            GeoCodeEventCompleted(this, e);

        }

    }

}

 

Below is the EventArgs sub class I use to pass the “processed” information back, along with the geo coordinate class.

 

public class GeoCodeEventArgs : EventArgs

{

    private int streetId;

 

    public GeoCodeEventArgs(int streetId)

    {

        this.streetId = streetId;

    }

 

    public int StreetId

    {

        get { return this.streetId; }

    }

}

 

public class GeoCoordinate

{

    public int Latitude { get; set; }

    public int Longitude { get; set; }

}

 

This is my command pattern implementation that will run on a separate worker thread and call the GeoProcessor.

 

internal class GeoWorker

{   

    GeoCoder geoCoder;

 

    int latitude;

    int longitude;

       

    public GeoWorker(GeoCoder geoCoder, int latitude, int longitude)

    {               

        this.latitude = latitude;

        this.longitude = longitude;

 

        this.geoCoder = geoCoder;

    }

   

    public void Process()

    {

        var geo = new GeoProcessor();

        geo.GeoCodeEventCompleted += new GeoProcessor.GeoCodeEventHandler(geo_GeoCodeEventCompleted);

       

        geo.QueueGeo(latitude, longitude);

    }

 

    void geo_GeoCodeEventCompleted(object sender, GeoCodeEventArgs e)

    {

        geoCoder.Data = e;

        geoCoder.GeoDone.Set();

    }   

}

 

 

My GeoCoder class is now only responsible for starting the worker thread that will call the GeoProcessor and then waiting for the reset event to be set.  Once set (when the worker handles the event from the GeoProcessor and calls Set() on the shared Reset Event) GeoCoder can continue.

 

public class GeoCoder

{

    ManualResetEvent geoDone;              

    Thread geoWorkerThread;

 

    int latitude;

    int longitude;

   

    public GeoCoder()

    {

        geoDone = new ManualResetEvent(false);      

    }

 

    public GeoCodeEventArgs Data { get; set; }

    public ManualResetEvent GeoDone { get; }

 

    public int ReverseGeoCode(int latitude, int longitude)

    {

        this.latitude = latitude;

        this.longitude = longitude;

 

        GeoDone.Reset();

 

        geoWorkerThread = new Thread(new ThreadStart(this.CallGeoWorker));

        geoWorkerThread.Name = "Reverse Geo Code Worker";

        geoWorkerThread.SetApartmentState(ApartmentState.STA);

        geoWorkerThread.Start();

 

        GeoDone.WaitOne();

        if (Data != null)

        {

            return Data.StreetId;          

        }

        return 0;

    }

 

    private void CallGeoWorker()

    {

        var geoWorker = new GeoWorker(this, latitude, longitude);

        geoWorker.Process();

    }

}

 

The asynchronous (event) call has now been turned into synchronous.

 

The biggest problem is the showstopper though.  COM doesn’t play well using this model, thanks to it’s STA threading (I believe) and the event handler never gets called. 

 

Overall a great learning experience, but I still can’t call the COM wrapper object synchronously and now, admitting defeat, I am rolling my own class to calculate the StreetId.

 

 

 

Posted on Thursday, January 29, 2009 2:29 PM | Back to top


Comments on this post: Turning an Asynchronous call into a Synchronous call

No comments posted yet.
Your comment:
 (will show your gravatar)
 


Copyright © Robert Gray | Powered by: GeeksWithBlogs.net | Join free