|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe purpose of this article is to demonstrate how to expose a method from an ASP.NET Web Service or WinFX Service that uses the asynchronous pattern internally, to increase the scalability of the service. Specifically, how to prevent the threads that are being used to service requests from blocking while background I/O operations take place. Please note, this article does not deal with applications simply making asynchronous calls to Web Services, as this has been amply covered already. However, if you are familiar with that, you'll notice plenty of similarities between the client-side and the server-side implementation. Contents
RequirementsI've written this using Visual Studio 2005, .NET 2.0, and the WinFX Feb CTP, on a Windows XP machine. The ASP.NET Web Service code should run on .NET 1.0 and .NET 1.1, although I haven't tested this. BackgroundBefore we start, let's just recap what normally happens when a consumer makes a request against an ASP.NET Web Service. The consumer issues a SOAP request to IIS which will hand the request over to the ASMX handler. If it hasn't already done so, the ASMX handler will build/reflect over the assemblies in the bin folder, looking for the methods decorated with the Writing the code - ASP.NET Web ServiceBefore we start writing any code to exploit the asynchronous features of the hosting platform, we need to create a synchronous method that performs some kind of I/O. I've chosen file-based I/O, but the principles apply equally to network I/O, such as a call to a backend ASP.NET Web Service or database. Below is the code for a method that opens a large file and counts the number of zero bytes inside it. I've called it [WebMethod]
public Int32 SyncWork()
{
FileStream fs =
new FileStream(@"c:\windows\fonts\arialuni.ttf",
FileMode.Open);
try
{
Byte[] buffer = new Byte[fs.Length];
fs.Read(buffer, 0, (Int32) fs.Length);
Int32 count = 0;
foreach (Byte b in buffer)
{
if (b == 0) count++;
}
return count;
}
finally
{
fs.Close();
}
}
Looking at I'm going to create a new method on the service called [WebMethod]
public IAsyncResult BeginAsyncWork(AsyncCallback callback, Object state)
{
...
}
[WebMethod]
public Int32 EndAsyncWork(IAsyncResult ar)
{
...
}
Notice that the Crucially, the
The consumer issues an A state class needs to maintain several pieces of information. It must know about the callback and state that was passed into the public class ServiceState : IAsyncResult
{
// callback and state object for the host
public AsyncCallback HostCallback;
public Object HostState;
// information needed for the background I/O call
public Byte[] Buffer;
public FileStream Stream;
public IAsyncResult ReadOperationAsyncResult;
// implementation of the IAsyncResult interface for the host
public object AsyncState { get { return HostState; } }
public WaitHandle AsyncWaitHandle { get {
return ReadOperationAsyncResult.AsyncWaitHandle; } }
public Boolean CompletedSynchronously { get {
return ReadOperationAsyncResult.CompletedSynchronously; } }
public Boolean IsCompleted { get {
return ReadOperationAsyncResult.IsCompleted; } }
// our callback method that will be
// notified when the background I/O is done
public void ReadOperationComplete(IAsyncResult ar)
{
ServiceState serviceState = ar.AsyncState as ServiceState;
serviceState.HostCallback(serviceState);
}
}
Notice that this implementation of the So, let's actually write the [WebMethod]
public IAsyncResult BeginAsyncWork(AsyncCallback callback, Object state)
{
ServiceState serviceState = new ServiceState();
serviceState.HostCallback = callback;
serviceState.HostState = state;
serviceState.Stream = new
FileStream(@"c:\windows\fonts\arialuni.ttf", FileMode.Open);
try
{
serviceState.Buffer = new Byte[serviceState.Stream.Length];
serviceState.ReadOperationAsyncResult =
serviceState.Stream.BeginRead(serviceState.Buffer, 0,
(Int32)serviceState.Stream.Length,
new AsyncCallback(serviceState.ReadOperationComplete), serviceState);
return serviceState;
}
catch (IOException)
{
serviceState.Stream.Close();
throw;
}
}
When the background I/O operation is complete, it will call back to the public void ReadOperationComplete(IAsyncResult ar)
{
ServiceState serviceState = ar.AsyncState as ServiceState;
serviceState.HostCallback(serviceState);
}
The ASMX handler will call [WebMethod]
public Int32 EndAsyncWork(IAsyncResult ar)
{
ServiceState serviceState = (ServiceState)ar;
try
{
serviceState.Stream.EndRead(serviceState.ReadOperationAsyncResult);
}
finally
{
serviceState.Stream.Close();
}
Int32 count = 0;
foreach (Byte b in serviceState.Buffer)
{
if (b == 0) count++;
}
return count;
}
Writing the code - WinFX ServiceTo get this working under WinFX requires a few very minor modifications. Rather than creating a new project and a new class, I'm going to convert the existing class because it's quicker. I'm using the Feb CTP build for this. First, we need to define a contract for our service, which is done by simply defining an interface decorated with the [ServiceContract()]
public interface IWinfxService
{
[OperationContract]
Int32 SyncWork();
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginAsyncWork(AsyncCallback callback, Object state);
Int32 EndAsyncWork(IAsyncResult ar);
}
The first method in our contract is the Next, we need to indicate that our public class Service : System.Web.Services.WebService, IWinfxService
{
...
}
Next, we need an entry point for our WinFX host, which in this case is IIS. To do this, we create a Service.svc file in the root of the website and put in the line of text shown below. This line instructs the WinFX host to look for a class called <% @ServiceHost Language="C#" Debug="true"
Service="Service" CodeBehind="~/App_Code/Service.cs" %>
Finally, we need to put a few extra lines into the Web.Config file. Again, if you had used Visual Studio and the 'Add New Item' dialog to create your WinFX service, then this would be done for you. The section that needs to be added to the <system.serviceModel>
<services>
<service name="Service" behaviorConfiguration="returnFaults">
<endpoint contract="IWinfxService" binding="wsHttpBinding"/>
</service>
</services>
<behaviors>
<behavior name="returnFaults"
returnUnknownExceptionsAsFaults="true" />
</behaviors>
</system.serviceModel>
Now, we just need to test it. First, check that the WinFX service builds OK by setting the website as the startup project and running it. When the IE window pops up, navigate to the service.svc page. Highlight the full url, which will be something like http://localhost:1234/WebSite2/Service.svc, and press Ctrl-C. Next, create a new console application. Then, right-click on the project and select 'Add Service Reference'. In the dialog that appears, paste the URL into the top box and type 'localhost' in the bottom box, and click OK. This sets up a reference to your service. In the Feb CTP of WinFX, there's no support for browsing to a WinFX service in your solution but this will almost certainly improve in newer builds. Finally, replace the auto-generated class Program
{
static void Main(string[] args)
{
localhost.WinfxServiceProxy proxy =
new localhost.WinfxServiceProxy();
Console.WriteLine(proxy.AsyncWork());
Console.ReadKey();
}
}
If you have any problems running the SummaryI've shown how to create a service method using the asynchronous pattern to increase the scalability of services that need to perform background I/O operations whether that be implemented using an ASP.NET Web Service or a WinFX service. History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||