views:

71

answers:

4

Long post.. sorry

I've been reading up on this and tried back and forth with different solutions for a couple of days now but I can't find the most obvious choice for my predicament.

About my situation; I am presenting to the user a page that will contain a couple of different repeaters showing some info based on the result from a couple of webservice calls. I'd like to have the data brought in with an updatepanel (that would be querying the result table once per every two or three seconds until it found results) so I'd actually like to render the page and then when the data is "ready" it gets shown.

The page asks a controller for the info to render and the controller checks in a result table to see if there's anything to be found. If the specific data is not found it calls a method GetData() in WebServiceName.cs. GetData does not return anything but is supposed to start an async operation that gets the data from the webservice. The controller returns null and UpdatePanel waits for the next query.

When that operation is complete it'll store the data in it's relevant place in the db where the controller will find it the next time the page asks for it.

The solution I have in place now is to fire up another thread. I will host the page on a shared webserver and I don't know if this will cause any problems..

So the current code which resides on page.aspx:

Thread t = new Thread(new ThreadStart(CreateService));
        t.Start();
    }

    void CreateService()
    {
        ServiceName serviceName = new ServiceName(user, "12345", "MOVING", "Apartment", "5100", "0", "72", "Bill", "rate_total", "1", "103", "serviceHost", "password");

    }

At first I thought the solution was to use Begin[Method] and End[Method] but these don't seem to have been generated. I thought this seemed like a good solution so I was a little frustrated when they didn't show up.. is there a chance I might have missed a checkbox or something when adding the web references?

I do not want to use the [Method]Async since this stops the page from rendering until [Method]AsyncCompleted gets called from what I've understood.

The call I'm going to do is not CPU-intensive, I'm just waiting on a webService sitting on a slow server, so what I understood from this article: http://msdn.microsoft.com/en-us/magazine/cc164128.aspx making the threadpool bigger is not a choice as this will actually impair the performance instead (since I can't throw in a mountain of hardware).

What do you think is the best solution for my current situation? I don't really like the current one (only by gut feeling but anyway)

Thanks for reading this awfully long post..

A: 

You can do this:

var action = new Action(CreateService);
action.BeginInvoke(action.EndInvoke, action);

or use ThreadPool.QueueUserWorkItem.

If using a Thread, make sure to set IsBackground=true.

There's a great post about fire and forget threads at http://consultingblogs.emc.com/jonathangeorge/archive/2009/09/10/make-methods-fire-and-forget-with-postsharp.aspx

Mikael Svenson
@Mikael - Thanks for the great tip, I'll try this as soon as possible. Do you know if this solution would become a bottleneck in a situation of a high concurrent user load?
Phil
These options will all use one thread per asynchronous operation.
Stephen Cleary
A: 

I'd encourage a different approach - one that doesn't use update panels. Update panels require an entire page to be loaded, and transferred over the wire - you only want the contents for a single control.

Consider doing a slightly more customized & optimized approach, using the MVC platform. Your data flow could look like:

  1. Have the original request to your web page spawn a thread that goes out and warms your data.
  2. Have a "skeleton" page returned to your client
  3. In said page, have a javascript thread that calls your server asking for the data.
  4. Using MVC, have a controller action that returns a partial view, which is limited to just the control you're interested in.

This will reduce your server load (can have a backoff algorithm), reduce the amount of info sent over the wire, and still give a great experience to the client.

Kevin
I like this approach but I'm weary of the development cost to change from MasterPages to the MVC model as I haven't read up or tried it out yet. Do I need to make a complete rehaul of the solution to be able to use asp.net mvc?
Phil
+2  A: 

Interesting. Until your question, I wasn't aware that VS changed from using Begin/End to Async/Completed when adding web references. I assumed that they would also include Begin/End, but apparently they did not.

You state "GetData does not return anything but is supposed to start an async operation that gets the data from the webservice," so I'm assuming that GetData actually blocks until the "async operation" completes. Otherwise, you could just call it synchronously.

Anyway, there are easy ways to get this working (asynchronous delegates, etc), but they consume a thread for each async operation, which doesn't scale.

You are correct that Async/Completed will block an asynchronous page. (side note: I believe that they will not block a synchronous page - but I've never tried that - so if you're using a non-async page, then you could try that). The method by which they "block" the asynchronous page is wrapped up in SynchronizationContext; in particular, each asynchronous page has a pending operation count which is incremented by Async and decremented after Completed.

You should be able to fake out this count (note: I haven't tried this either ;) ). Just substitute the default SynchronizationContext, which ignores the count:

var oldSyncContext = SynchronizationContext.Current;
try
{
  SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
  var serviceName = new ServiceName(..);
  // Note: MyMethodCompleted will be invoked in a ThreadPool thread
  //  but WITHOUT an associated ASP.NET page, so some global state
  //  might be missing. Be careful with what code goes in there...
  serviceName.MethodCompleted += MyMethodCompleted;
  serviceName.MethodAsync(..);
}
finally
{
  SynchronizationContext.SetSynchronizationContext(oldSyncContext);
}

I wrote a class that handles the temporary replacement of SynchronizationContext.Current as part of the Nito.Async library. Using that class simplifies the code to:

using (new ScopedSynchronizationContext(new SynchronizationContext()))
{
  var serviceName = new ServiceName(..);
  // Note: MyMethodCompleted will be invoked in a ThreadPool thread
  //  but WITHOUT an associated ASP.NET page, so some global state
  //  might be missing. Be careful with what code goes in there...
  serviceName.MethodCompleted += MyMethodCompleted;
  serviceName.MethodAsync(..);
}

This solution does not consume a thread that just waits for the operation to complete. It just registers a callback and keeps the connection open until the response arrives.

Stephen Cleary
Thanks, I really appreciate it. I'll try this tomorrow. As a side-note, aspx-pages that don't use the async "feature" are not allowed to call async methods. At least not in my situation, since I get a runtime error stating this when the method gets called.
Phil
And just so I get this straight, I'm supposed to trick IIS into rendering the page even though it's supposed to be blocked? Sounds fun :)
Phil
Yes; that about sums it up.
Stephen Cleary
This worked nicely. I wish I fully understood the solution though.. I guess it comes with experience!
Phil
I proposed an article on SyncCtx for MSDNMag a couple months ago.. none of my emails have been answered. Anyway, here's a 450-char description ;) ASP.NET applies a SyncCtx to its ThreadPool threads. This SyncCtx maintains a count of outstanding operations per page. The Async/Completed system is an [event-based asynchronous pattern](http://msdn.microsoft.com/en-us/library/wewwczdw.aspx) which uses SyncCtx (Begin/End does not). Non-ASP.NET ThreadPools use the default SyncCtx which ignores the *OperationStarted* and *OperationCompleted* signals. We're just using that SyncCtx instead of ASP.NET's.
Stephen Cleary
very clever.. thank you! I hope MSDN will respond and publish that article :) When they do; bump me!
Phil
A: 

try using [WebMethod] [SoapDocumentMethod(OneWay = true)] void MyAsyncMethod(parameters) { }

in your web service

but be careful if you use impersonation, we had problems on our side.

Alex
I understand by 'in your web service' that you say that I should modify the web service method? I don't have any control over the webservice and I believe it's written in php but I really don't know.
Phil