Study/WebService2008. 2. 20. 01:31

Download source and demo project - 10.3 Kb

Sample Image

Introduction

The 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

Requirements

I'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.

Background

Before 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 WebMethod attribute. The ASMX handler can then match a SOAP request with a specific method, and hence deserialises the request into the input parameters of that method. When that method returns, the thread running it is returned to the pool, and the associated HttpContext is released.

Writing the code - ASP.NET Web Service

Before 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 SyncWork. The large file I've chosen is the TrueType Arial font because it's installed on most XP machines and weighs in at 22 MB, but any large file will do.

[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 SyncWork, we can identify that the call to FileStream.Read is going to involve reading some data from the hard disk. This is going to take some time to complete, and while we're waiting, our thread will be blocked and hence not performing any useful work. It would be better if this thread could be returned to the thread pool managed by our host (in this case IIS, or specifically the ASMX handler) so that more requests could be served. This is a scenario where implementing server-side asynchronous methods can help to make our service more scalable.

I'm going to create a new method on the service called AsyncWork which will perform the same function for the consumer as SyncWork but it will behave asynchronously. Although AsyncWork will appear to the consumer as a single method, we have to write two separate methods, named BeginAsyncWork and EndAsyncWork, respectively. The required signatures for these methods are shown below:

[WebMethod]
public IAsyncResult BeginAsyncWork(AsyncCallback callback, Object state)
{
    ...
}

[WebMethod]
public Int32 EndAsyncWork(IAsyncResult ar)
{
    ...
}

Notice that the BeginAsyncWork method has two additional input parameters, appended to the end of the parameter list. In this case, they are the only two input parameters. The first parameter refers to a callback method that should be called when the background I/O work is complete. The state object is anything that the host would like us to associate with this asynchronous work so that it can identify it later. In my experience, this has always been null, but there's nothing to suggest it won't be used in the future so it's important to treat it properly. The BeginAsyncWork method must return an IAsyncResult. This same IAsyncResult will be passed as the sole parameter to the EndAsyncWork method when the background I/O operation is complete. The EndAsyncWork method will have the same return type as the SyncWork method, in this case an Int32.

Crucially, the EndAsyncWork method may be called on a different instance of our Service class than the call to BeginAsyncWork. This is because the thread on which the BeginAsyncWork code ran will be returned to the thread pool as soon as it is finished so that it can be used to service another request - the whole point of this exercise in fact! A thread won't be selected to run the EndAsyncWork code until the ASMX handler receives notification that the background I/O operation is complete. All of this grabbing and releasing of threads means that we can't share information between the BeginAsyncWork and EndAsyncWork methods using member variables on our Service class. We'll need to create a separate class to hold state information, and give it to the ASMX handler to look after for us. Since the ASMX handler is going to get an object that implements IAsyncResult from BeginAsyncWork, and it's going to pass this object to EndAsyncWork, it makes sense for our state to be contained in that object. So in short, we'll store our state information in the object that implements IAsyncResult. The sequence diagram below shows the messages that are sent between the various objects during the process:

The consumer issues an AsyncWork request, which causes the ASMX handler to call BeginAsyncWork, which creates a state object and starts the background I/O operation via the FileStream's BeginRead method. The service1 object is finished so it can be garbage collected and its thread returned to the pool. When the background I/O operation finishes, it calls the ReadOperationComplete method on the serviceState object which in turn invokes the ASMX handler's callback method. The ASMX handler responds to this notification by calling the EndAysncWork method which finishes the job. Notice in the diagram above that the two Service classes, service1 and service2, have short life-times with respect to the whole operation (as shown in yellow).

A state class needs to maintain several pieces of information. It must know about the callback and state that was passed into the BeginAsyncWork method. It must reference the variables shared between the BeginAsyncWork and EndAsyncWork methods, which in this case boils down to a FileStream, an IAsyncResult returned from the FileStream, and a Byte array. It must implement the IAsyncResult interface so that it can be returned to the ASMX handler. Finally, it must provide a method which the background operation (FileStream, in this case) can call to tell us that it's finished. The ServiceState class below satisfies these requirements:

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 IAsyncResult interface is achieved by simply wrapping most of the properties of the ReadOperationAsyncResult member. This member is provided by the FileStream object when calling BeginRead, and provides information about that specific background operation. Since this is our only background operation, we can just wrap it. If we had multiple background operations, we would need to implement IAsyncResult ourselves with properties that reflected the complete picture. For example, if only one of two background operations had completed, then the IsCompleted property would need to return false. The AsyncState property returns the HostState object. This is because if the host was to access the AsyncState property, then it would expect to get the same object that it provided in the initial call to BeginAsyncWork.

So, let's actually write the BeginAsyncWork method. We begin by constructing the object that will contain our state information, a ServiceState object in this case. I've stored the callback and the state. I've then created the FileStream, and subsequently the Byte array, that I need for the operation, and attached these to the state also. I then start the background I/O operation by providing the input parameters for both the read operations (a Byte array, start point, number of bytes) and for the asynchronous facility (a callback and my state object). My state object, serviceState, is then returned as this implements the IAsyncResult interface and allows the host to keep track of the background operation.

[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 ServiceState.ReadOperationComplete method. Taking a look at that method again below, you can see that I'm extracting the serviceState object that I provided, and I'm using it to access the HostCallback. By invoking this method, I'm letting the ASMX handler know that the job is done and that the EndAsyncWork method should now be called.

public void ReadOperationComplete(IAsyncResult ar)
{
    ServiceState serviceState = ar.AsyncState as ServiceState;
    serviceState.HostCallback(serviceState);
}

The ASMX handler will call EndAsyncWork, and this is where the original request made by one of the consumers of our service should be fulfilled. The method below shows how this is done. First, I extract the serviceState, and then call the FileStream.EndRead method to complete the Read operation. Finally, the serviceState object is accessed again in order to work on the Byte buffer that has been populated so as to determine the number of zero bytes. This value is returned from the method, and hence to the consumer, and this request has now been fulfilled.

[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 Service

To 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 attribute. The interface for our service is shown below. I've called it IWinfxService.

[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 SyncWork method. As you might expect, it has the same signature as the SyncWork method defined on the Service class, but it is also decorated with the OperationContract attribute. For methods that the WinFX host (in this case, still IIS) is to treat asynchronously, we need to set the AsyncPattern property of the OperationContract to true. As a result of doing this, we no longer need to decorate the EndAsyncWork method with any attributes but it does need to be present in the interface.

Next, we need to indicate that our Service class implements this interface. We do this by appending IWinfxService to the class declaration.

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 Service in a file called ~/App_Code/Service.cs. If you create your WinFX services using the 'Add New Item' dialog, then this will normally be created for you, but here we're converting an ASP.NET Web Service so we have to create it manually.

<% @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 Configuration node is shown below. Please bear in mind that this is one possible configuration - you can change the binding used for the service, and you would certainly want to change the behaviour such that it didn't return detailed faults to the consumer.

<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 Program class with the one below to test your WinFX service. You can use break-points to satisfy yourself that the service is using the asynchronous pattern internally.

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 ConsoleApplication test harness included in the download, then it's probably because, on your computer, the website has been assigned a different port number. One way to fix this is to just remove and re-add the Service Reference using the instructions explained two paragraphs above.

Summary

I'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

  • No changes made so far.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Karl M. Hulme


I live with my wife in Bournemouth in the UK. Her shining brilliance consistently inspires me to be the person I know I should be. I enjoy writing music, playing the piano, watching movies, philisophical discussion and eating jaffa cakes.

In my spare time I work for Regency as ICT Manager, dividing my time between IT strategy, software architecture, development, infrastructure, networking and crawling around under desks.
Occupation: Web Developer
Location: United Kingdom United Kingdom
출처 : CodeProject.com (http://www.codeproject.com/KB/cpp/asyncws.aspx)
Posted by 굥쓰
Study/WebService2008. 2. 20. 01:28
by Peter A. Bromberg, Ph.D.

Peter Bromberg
"Watching non-programmers trying to run software companies is like watching someone who doesn't know how to surf trying to surf." -- Joel Spolsky

We've been getting a few of this genre of questions recently on our forums, and I decided it would be a good time to put something together. And since we seem to be getting a lot of VB.NET'ers, I've broken with tradition and will post code in both VB.NET and C#, as well as in the downloadable solution.

First let's ask and answer a few basic questions:

Q: What does "Asynchronous" really mean?

A: Literally, "not synchronous"; not at the same time. Asynchronous is a programming term that means a type of two-way communication that occurs with a time delay, allowing participants to respond at their own convenience. In other words, we can make a method call and go about our work without delay, and when the results are ready, we are notifed appropriately and are able to act upon them.


Bug Tracking Saves Time
Save hundreds of development hours each month. Click to see how...

GoDiagram Components
Add diagrams to improve your user interface, allowing users to more easily visualize and manipulate their data.

Demo Builder - Create Flash Presentations
Create interactive Flash movies that allow you to show how applications and systems work.  Download a FREE trial now.

Q: When would I want to use asynchronous web service methods?

A: Whenever you want! Particularly, if the method call may take some time and you do not want to have your user interface "frozen" while waiting for the results. Asynchronous WebMethod calls are as efficient, and sometimes more efficient than their synchronous siblings.

In ASP.NET, starting with ASP.NET 1.0, asynchronous versions of WebMethods are wired into the WebService infrastructure automatically. On the server side, you need do nothing except create your WebMethod, and the WSDL contract that .NET uses to generate a proxy class to make calls on service methods will automatically include asynchronous versions of each method. You can identify these because they begin with "Begin"<WebMethodName> and "End"<WebMethodName>.

Q: How does all this work?

A: Asynchronous methods in webservices work the same way as asynchronicity in any other API - the inbound method call is intercepted and acted upon. However, the results are sent via what is referred to as a "Callback". A callback is a method on the client which is "rung up" or called back, with the results of the call. What this means is that a method call to a BeginXXX WebMethod returns immediately, it is said to be "non blocking". This allows your UI to remain free so the user continues to have full use of its features while waiting for the result. When the result arrives, it is the separate callback method that receives and processes it.

Now let's take a look at some simple ASP.NET 1.1 VB.NET code for a WebMethod that accepts a string, and packages it up, returning a DataSet object as the return type:


    <WebMethod()> _

    Public Function GetDataSet(ByVal value As String) As DataSet

        Dim ds As New DataSet

        Dim dt As New DataTable

        dt.Columns.Add("Test")

        Dim row As DataRow

        row = dt.NewRow

        row("Test") = value

        dt.Rows.Add(row)

        ds.Tables.Add(dt)

        Return ds

    End Function

The above is quite simple and should require no explanation. However, if we take a look at the proxy code that is generated by creating a WebReference to this service, we see:

Those two artful red arrows show the Asynchronous version set of methods that ASP.NET automatically generated for us to match our synchronous "GetDataSet" method.

Consuming Asynchronous Methods

Now comes the fun part - how to use these new guys!

As with any WebService method call, we need an instance of our proxy class. But that's where things change. Consider the following sample VB.NET code from a Windows Forms application that consumes our "GetDataSet" method:

 ' This is our button click handler. It creates the service proxy instance, and calls the BEGINXXX method:

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim ws As New peter.Service1

        Dim ar As IAsyncResult = ws.BeginGetDataSet("Testing 1234", New AsyncCallback(AddressOf WsCallback), ws)

    End Sub


    ' This is our Callback method. It RECEIVES the callback in the form of an IAsyncResult object we can act on.

    Private Sub WsCallback(ByVal ar As IAsyncResult)

        Dim cb As peter.Service1 = ar.AsyncState

        Dim ds As DataSet = DirectCast(cb.EndGetDataSet(ar), DataSet)

        Me.Label1.Text = ds.Tables(0).Rows(0)("Test")

    End Sub

Note that the "BeginGetDataSet" method call has a distinctive signature - the string input parameter is passed first (there could be more than one parameter) , then we create a new AsyncCallback object that "points" to the WsCallback callback (the "receiver") method. This Button1_Click method returns immediately - there is no blocking of the thread. Note also that in this case I decided to pass the actual "ws" proxy class instance itself as the last parameter, which is the AsyncState parameter, of type Object.

In the WsCallback method, we receive the IAsyncResult and I immediately get back my original proxy class instance by casting it out of the IAsyncResult object's AsyncState property. Then, all I need to do is call the asynchronous "EndGetDataSet" method, passing in the AsyncResult object, and cast this to the return type of DataSet.

At this point I have a valid DataSet object and I can do whatever I "am supposed to do" with it - update a DataGrid, whatever. In this simplified example I simply get out my one column value from my single DataTable row and update the Label with it.

This infrastructure has a number of variations, including the use of delegates. Also note that the return value of the original ws.BeginGetDataSet method call is also an IAsyncResult object, and this can be used as well.

While there are a number of variations on this theme, the above pattern and signatures are useful in the vast majority of instances.

Also, I think it is important to bear in mind that in ASP.NET 2.0, we have some interesting enhancements, most notably that there is now an Event-driven WebService infrastructure, which makes for a much cleaner use pattern on the client / consumer side. In addition, it provides the ability to work with servers that enable HTTP (Gzip) compression to conserve bandwidth.

The C# Consumer pattern looks like this:

private void button1_Click(object sender, System.EventArgs e)

        {

            peter.Service1 ws = new CSharpTester.peter.Service1();

            ws.BeginGetDataSet("Testing 1234",new AsyncCallback(WsCallback),ws);

        }



        private void WsCallback( IAsyncResult ar)

        {

            peter.Service1 ws =  (peter.Service1)ar.AsyncState;

            DataSet ds = ws.EndGetDataSet(ar);

            this.label1.Text=(string)ds.Tables[0].Rows[0]["test"];

        }

The downloadable Visual Studio.NET 2003 solution below has a VB.NET Service page, and both C# and VB.NET versions of a Winforms consumer application.

Download the Visual Studio.NET 2003 Solution that accompanies this article

Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here.  --> Social Link this page<--NOTE: Post QUESTIONS on FORUMS!


Posted by 굥쓰
Study/MSDN2008. 2. 20. 01:23
 

기본 XML Web Services

Roger Wolter
Microsoft Corporation

요약: 이 기사는 SOAP, WSDL, UDDI 소개 및 개발자용 XML Web services 값의 개요를 설명합니다(6페이지/인쇄 페이지 기준).

목차

XML Web Service
SOAP
WSDL
UDDI
기타

XML Web Service

XML Web services는 인터넷상에서 분산된 컴퓨팅으로 이동하는 데 기본이 되는 빌딩 블록입니다. 열린 표준과 통신 중심, 작업자 및 응용 프로그램간의 공동 작업으로 응용 프로그램을 통합하는 데 XML Web services가 플랫폼이 되는 환경을 만들었습니다. 응용 프로그램은 위치나 구현 방법과 상관없이 함께 작동하는 다양한 소스의 여러 XML Web services를 사용하여 구성됩니다.

XML Web Service를 만드는 회사가 많은 만큼 XML Web Service의 정의도 다양하겠지만 모든 정의에는 다음과 같은 공통점이 있습니다.

  • XML Web Services는 표준 웹 프로토콜을 통해 웹 사용자들에게 유용한 기능을 표시합니다. 대부분의 경우 SOAP 프로토콜이 사용됩니다.
  • XML Web services는 사용자와 대화할 수 있는 클라이언트 응용 프로그램을 만들 수 있는 인터페이스를 세부적으로 설명하는 방법을 제공합니다. 일반적으로 이 설명은 Web Services 설명 언어(WSDL) 문서라고 하는 XML 문서에 제공됩니다.
  • XML Web services는 잠재적인 사용자가 XML Web services를 쉽게 찾을 수 있도록 등록됩니다. XML Web services는 UDDI(Universal Discovery Description and Integration)에 등록됩니다.

본 기사에서 위의 세가지 기술을 모두 설명하겠지만 XML Web services에 주의를 기울여야 하는 이유를 먼저 설명하겠습니다.

XML Web services 아키텍처의 주요 장점 중 하나는 별도의 플랫폼에 별도의 언어로 작성된 프로그램들이 표준 기반으로 서로 통신할 수 있다는 점입니다. 이 점에 대해 산업 분야에 종사했던 사람들은 DEC 이전과 CORBA에서도 같은 내용이 있었기 때문에 차이점이 없을 것 같다는 의문이 생길 수 있습니다. 첫 번째 차이점은 SOAP가 이전의 접근 방법보다 훨씬 간단하여 표준 규격의 SOAP 구현을 입력하는 작업이 더욱 용이하다는 것입니다. Paul Kulchenko는 79항목이 포함된 마지막 카운트인 http://www.soapware.org/directory/4/implementations 에서 SOAP 구현 목록을 유지 관리합니다. SOAP 구현은 규모가 큰 소프트웨어 회사에서 찾을 수 있지만 개인 개발자가 작성하고 유지 관리하는 구현도 많이 찾을 수 있습니다. 이전에 비해 XML Web services가 갖는 또 다른 큰 장점은 표준 웹 프로토콜인 XML, HTTP 및 TCP/IP와 작동한다는 것입니다. 수 많은 회사들이 웹 인프라와 이를 유지 관리하는 지식과 경험이 있는 직원들을 갖추었기 때문에 XML Web services에 대한 항목에 소모되는 비용이 이전 기술에 비해 현저히 감소됩니다.

지금까지 WSDL 파일에 설명되고 UDDI에 등록된 XML Web service를 SOAP를 통해 웹에 나타나는 소프트웨어 서비스로 정의했습니다. 그 다음 논리적인 질문은 XML Web services로 무엇을 할 수 있는가 하는 것입니다. 처음에 XML Web services는 주식 시세, 일기 예보, 스포츠 등 응용 프로그램에 쉽게 통합될 수 있는 정보원이었습니다. 원하는 정보를 분석하고 집계하여 다양한 방법으로 표시하도록 작성할 수 있는 응용 프로그램이 있습니다. 예를 들어, Microsoft® Excel 스프레드시트를 사용하여 주식, 401K, 은행 계좌, 대출 등의 전체 금융 상태를 요약할 수 있습니다. 이 정보를 XML Web services에서 사용할 수 있는 경우 Excel은 이 Web services를 계속 업데이트합니다. 이 정보 중 일부는 무료이며 어떤 정보는 서비스 구독이 필요할 수도 있습니다. 대부분의 정보는 웹에서 현재 사용할 수 있지만 XML Web services는 더욱 쉽고 안정적인 프로그래밍 방식으로 액세스됩니다.

기존 응용 프로그램을 XML Web services로 나타내면 XML Web services를 빌딩 블록으로 사용하는 더욱 강력한 새 응용 프로그램을 작성할 수 있습니다. 예를 들어, 여러 공급업체의 가격 정보를 자동으로 얻을 수 있고 공급업체를 선택할 수 있으며 주문한 다음 제품이 도착할 때까지 배달 과정을 확인할 수 있습니다. 공급업체 응용 프로그램은 웹상에 서비스를 표시한 다음 XML Web services를 사용하여 고객의 신용 확인, 요금 부과 및 배달 회사에 배달을 의뢰할 수도 있습니다.

앞으로 몇몇 중요한 XML 웹 서비스를 사용하면 웹을 사용하는 응용 프로그램을 지원하여 오늘날에는 실행하지 못하는 사항들을 실행할 수 있게 될 것입니다. 예를 들어, 일정 관리 서비스는 Microsoft .NET My Services 프로젝트가 지원하는 서비스 중 하나입니다. 치과 의사나 정비사가 XML Web service를 사용하여 일정을 관리하는 경우 고객이 온라인으로 시간 약속을 예약하거나, 고객이 원할 경우 치과 의사나 정비사가 직접 고객과 청소 및 정기 점검 약속을 할 수 있습니다.조금만 생각해 보면, 웹을 프로그램할 수 있는 능력이 있기만 하면 작성할 수 있는 응용 프로그램이 많이 있습니다.

XML Web services 및 응용 프로그램 작성에 대한 더 자세한 내용은 MSDN Web services 홈 페이지를 참조하십시오.

SOAP

SOAP는 XML Web services용 통신 프로토콜입니다. SOAP를 통신 프로토콜로 설명하면 대부분의 사람들은 DCOM 또는 CORBA를 생각하고 "SOAP가 객체를 활성화하는 방법은 " 또는 "SOAP가 사용하는 이름 서비스는 " 등과 같은 질문을 합니다. SOAP 구현에 이러한 내용이 포함되기는 하지만 SOAP 표준은 이 내용을 지정하지 않습니다. SOAP는 메시지 즉, 요청된 사양의 일부에 대한 XML 형식을 정의하는 사양입니다. 몇몇 SOAP 요소에 포함된 올바른 형식의 XML 조각이 있는 경우 SOAP 메시지가 나타납니다. 간단합니다.

SOAP 사양의 다른 부분은 프로그램 데이터를 XML로 표시하고 SOAP를 사용하여 원격 프로시저 호출을 수행하는 방법을 설명합니다. 이러한 사양의 선택적 부품은 클라이언트에서 전송되는 SOAP 메시지에 호출 가능한 함수와 해당 함수에 전달되는 매개 변수가 포함되어 있는 RPC 스타일 응용 프로그램을 구현하는 데 사용되며 서버는 실행된 함수의 결과와 함께 메시지를 반환합니다. COM 또는 CORBA 응용 프로그램을 실행하는 프로그래머들은 RPC 스타일을 이해하기 때문에 현재 대부분의 SOAP 구현은 RPC 응용 프로그램을 지원합니다. 또한 SOAP는 SOAP 메시지가 XML 문서에서 래퍼 기능만 수행하는 문서 스타일 응용 프로그램도 지원합니다. 문서 스타일 SOAP 응용 프로그램은 매우 융통성이 있으며 여러 새 XML Web services는 이 장점을 활용하여 RPC를 사용하여 구현하기 어려운 서비스를 만듭니다.

SOAP 사양의 마지막 선택 부분은 SOAP 메시지를 포함한 HTTP 메시지의 형식을 정의합니다. HTTP 바인딩은 HTTP가 거의 모든 현재 OS 및 현재가 아닌 여러 OS에 의해 지원되기 때문에 중요합니다. HTTP 바인딩은 선택 사항이지만 SOAP에 대해 유일하게 표준화된 프로토콜이기 때문에 거의 모든 SOAP 구현은 HTTP 바인딩을 지원합니다. 이러한 이유로 인해 SOAP에 HTTP가 필요한 것으로 잘못 생각하는 경우가 있습니다. 몇몇 구현은 MSMQ, MQ 시리즈, SMTP 또는 TCP/IP 전송을 지원하지만 거의 모든 현재 XML Web services는 흔히 사용되는 HTTP를 사용합니다. HTTP는 핵심 웹 프로토콜이므로 대부분의 회사는 HTTP를 지원하는 네트워크 인프라와 이를 관리하는 인력을 갖추고 있습니다. 보안, 모니터링 및 HTTP에 대한 로드 균형 인프라는 오늘날 쉽게 사용할 수 있습니다.

SOAP를 시작할 때 혼동되는 주요 원인은 SOAP 사양과 여러 SOAP 사양 구현간의 차이점입니다. SOAP를 사용하는 대부분의 사람들은 SOAP 메시지를 직접 작성하지는 않지만 SOAP 도구 키트를 사용하여 SOAP 메시지를 만들고 구문 분석합니다. 일반적으로 이러한 도구 키트는 함수 호출을 몇몇 종류의 언어에서 SOAP 메시지로 변환합니다. 예를 들어, Microsoft SOAP Toolkit 2.0은 COM 함수 호출을 SOAP로 전환하고 Apache Toolkit는 JAVA 함수 호출을 SOAP로 변환합니다. 함수 호출의 유형과 지원되는 매개 변수의 데이터 유형은 각 SOAP 구현에 따라 다양하기 때문에 한 개의 도구 키트와 함께 작동하는 함수는 다른 도구 키트와 작동하지 않을 수도 있습니다. 이것은 SOAP의 제한 사항이 아니라 사용자가 사용하는 특정 구현입니다.

훨씬 강제적인 SOAP의 기능은 많은 별도의 하드웨어 및 소프트웨어 플랫폼에 구현되었다는 점입니다. 이것은 사용자의 회사 내부, 외부의 서로 다른 시스템을 연결하는 데 SOAP를 사용할 수 있다는 의미입니다. 과거에 시스템을 통합하는 일반 통신 프로토콜을 개발하려는 노력은 많았지만 아무도 SOAP를 일반적으로 채택하지는 않았습니다. 왜냐하면, SOAP는 이전의 많은 프로토콜에 비해 구현하는 데 훨씬 작고 단순했기 때문입니다. 예를 들어, DCE 및 CORBA는 구현하는 데 수 년이 걸렸기 때문에 소수의 구현만이 출시되었습니다. 그러나 SOAP는 기존의 XML 파서 및 HTTP 라이브러리를 사용하여 대부분의 힘든 작업을 수행하므로 수 개월 내에 SOAP 구현을 완료할 수 있습니다. 따라서 70종류의 SOAP 구현을 사용할 수 있습니다. SOAP는 DCE 또는 CORBA가 수행하는 작업을 모두 수행하지는 않지만 기능 교환이 복잡하지 않기 때문에 SOAP를 쉽게 사용할 수 있습니다.

HTTP의 보편성와 SOAP의 간소성이 동시에 존재하면 거의 모든 환경에서 호출할 수 있는 XML Web services를 구현하는 데 있어 이상적인 기반이 됩니다. SOAP에 대한 더 자세한 내용은 MSDN SOAP 홈 페이지를 참조하십시오.

보안

SOAP를 처음 사용하는 사람들의 첫 번째 질문 중 하나는 SOAP가 보안을 다루는 방법입니다. SOAP 개발 초기에는 SOAP가 HTTP 기반 프로토콜로 표시되어 HTTP 보안이 SOAP에 충분했습니다. 오늘날 수 많은 웹 응용 프로그램이 HTTP 보안을 사용하여 실행되므로 HTTP 보안은 SOAP에 충분합니다. 따라서 현재 SOAP 표준은 보안 문제보다는 전송 문제를 다룹니다.

SOAP가 여러 전송 작업에서 제대로 실행되는 보다 일반적인 용도의 프로토콜로 확장되면 보안은 더욱 큰 문제가 됩니다. 예를 들어, HTTP는 SOAP를 호출하는 사용자를 인증하는 몇 가지 방법을 제공하지만 메시지가 HTTP에서 SMTP로 전송될 때 해당 ID의 전파 방법에 대한 문제가 발생합니다. 다행히 SOAP는 빌딩 블록 프로토콜로 디자인되었기 때문에 SOAP를 작성하는 작업에 이미 사양이 포함되어 Web services에 대한 추가 보안 기능을 제공합니다. WS-Security specification 은 전체 암호화 시스템을 정의하고 WS-License specification 은 호출자의 ID와 권한이 부여된 사용자만 Web service를 사용할 수 있도록 하는 기술을 정의합니다.

WSDL

whiz-dull이라고도 하는 WSDL는 Web Services Description Language의 약어입니다. 편의상, WSDL 파일을 SOAP 메시지 집합 및 해당 메시지가 교환되는 방법을 설명하는 XML 문서라고 할 수 있습니다. 다시 말해서 WSDL은 IDL이 CORBA 또는 COM인 SOAP입니다. WSDL은 XML이기 때문에 읽을 수 있고 편집할 수 있지만 대부분의 경우에는 소프트웨어에 의해 작성되고 사용됩니다.

WSDL의 값을 보려면 비즈니스 파트너가 제공하는 SOAP 메서드를 호출해야 합니다. 비즈니스 파트너에게 몇몇 예제 SOAP 메시지를 요청하고 해당 예제와 같은 메시지를 작성하고 사용하는 응용 프로그램을 작성할 수 있으나, 오류가 발생하기 쉽습니다. 예를 들어, 고객 ID가 2387인 경우 메시지가 문자열이면 ID는 정수입니다. WSDL은 요청 메시지에 포함되는 사항과 응답 메시지의 형식을 명백한 노테이션으로 지정합니다.

WSDL 파일이 메시지 형식을 설명하기 위해 사용하는 노테이션은 XML 스키마 표준을 기반으로 하며, 이것은 프로그래밍 언어 중립적이며 또한 표준 기반이어서 다양한 플랫폼과 프로그래밍 언어에서 액세스할 수 있는 XML Web services 인터페이스를 설명하기에 적합하다는 것을 의미합니다. WSDL은 메시지 컨텐트를 설명할 뿐만 아니라 서비스를 사용할 수 있는 위치 및 서비스와 대화하는 데 사용되는 통신 프로토콜을 정의합니다. 즉, WSDL 파일은 XML Web service와 함께 작동하는 프로그램을 쓰는 데 필요한 모든 사항을 정의합니다. WSDL 파일을 읽고 XML Web service와 통신하는 데 필요한 코드를 생성할 수 있는 몇 가지 도구가 있습니다. 그 중에서 성능이 뛰어난 몇몇 도구들이 Microsoft Visual Studio® .NET에 있습니다.

많은 현재 SOAP 도구 키트에는 기존 프로그램 인터페이스에서 WSDL 파일을 생성하는 도구가 포함되지만 WSDL을 직접 작성하는 도구는 거의 없으며 WSDL에 대해 필요한 만큼 도구가 지원되지 않습니다. WSDL 파일 작성자에게 도구가 지원되어 COM IDL과 같은 프록시 및 스텁을 작성하는 것은 대부분의 SOAP 구현의 일부분이 됩니다. 이런 점에서 WSDL은 XML Web services에 대한 SOAP 인터페이스를 만드는 데 좋은 방법입니다.

http://www.w3.org/TR/wsdl 에서 WSDL 사양을 볼 수 있으며 우수한 description of WSDL을 사용할 수 있습니다.

UDDI

UDDI(Universal Discovery Description 및 Integration)는 Web services의 옐로 페이지입니다. 전통적인 옐로 페이지에서는 필요한 서비스를 제공하는 회사를 찾아 제공된 서비스를 검토한 후에 담당자와 연락하여 자세한 정보를 구할 수 있습니다. 지하실에서 비즈니스를 시작하고 말로 광고를 할 수 있는 것처럼 UDDI에 등록하지 않고도 Web service를 제공할 수는 있습니다. 그러나 시장을 넓히기 위해서는 고객이 Web services를 찾을 수 있도록 UDDI에 등록해야 합니다.

UDDI 디렉터리 항목은 비즈니스 및 제공되는 서비스를 설명하는 XML 파일입니다. UDDI 디렉터리의 항목은 세 부분으로 되어 있습니다. "화이트 페이지"는 서비스를 제공하는 회사의 이름, 주소, 연락처 등을 설명합니다. "옐로 페이지"에는 북미 산업 분류 시스템 및 표준 산업 분류 등과 같은 표준 분류법을 기반으로 하는 산업 범주가 나와 있습니다. "그린 페이지"는 Web service를 사용하는 응용 프로그램을 작성할 수 있도록 서비스에 인터페이스를 세부적으로 설명합니다. 서비스 방법은 유형 모델 또는 tModel이라고도 하는 UDDI 문서를 통해 정의됩니다. 많은 경우에, tModel에는 XML Web service에 SOAP 인터페이스를 설명하는 WSDL 파일이 포함되어 있지만 tModel은 융통성이 있어 거의 모든 종류의 서비스를 설명할 수 있습니다.

또한 UDDI 디렉터리에는 사용자의 응용 프로그램을 작성하는 데 필요한 서비스를 검색하는 몇 가지 방법이 있습니다. 예를 들어, 지정된 지역 및 지정된 유형의 비즈니스로 서비스 공급자를 검색할 수 있습니다. 그런 다음 UDDI 디렉터리는 정보, 연락처, 링크 및 기술적인 데이터를 공급하여 사용자의 요구 사항에 맞는 서비스를 평가할 수 있도록 합니다.

UDDI를 사용하여 Web services를 얻을 수 있는 비즈니스를 찾을 수 있습니다. 비즈니스 파트너는 알고 있지만 제공되는 서비스가 무엇인지 모를 경우에는 WS-Inspection specification 을 사용하여 특정 서버에 제공된 XML Web services의 컬렉션을 검색하여 필요한 서비스를 찾을 수 있습니다.

UDDI에 대한 더 자세한 내용은 http://www.uddi.org/about.html 를 참조하십시오.

기타

지금까지 XML Web services와의 대화 방법(SOAP), XML Web services 설명 방법(WSDL) 및 XML Web services 찾는 방법(UDDI)에 대해 설명했습니다. 이러한 사항들은 응용 프로그램 통계 및 집계에 대한 기초를 제공하는 기준 사양 집합을 구성합니다. 회사는 기준 사양을 이용하여 실제 솔루션을 구축하고 실제 값을 얻습니다.

실제 XML Web services를 만드는 데 많은 노력을 해왔지만 앞으로 더욱 많은 노력이 필요합니다. 오늘날 XML Web services를 성공적으로 작성하고는 있지만 보안, 운영 관리, 트랜잭션, 안정된 메시징과 같이 개발자가 개발해야 할 사항들이 여전히 남아 있습니다. 전역 XML Web Services 아키텍처는 모듈화 및 확장 가능한 XML Web services에 새 고급 기능을 추가하는 데 정확한 다용도 모델을 제공하여 XML Web services를 다음 단계로 이동할 수 있도록 도와줍니다.

위에서 언급한 보안 모듈(WS-Security WS-License) 은 전역 Web Services 아키텍처의 사양 중 일부입니다. 운영 관리를 프로세싱하려면 여러 서버 간에 메시지를 라우팅하고 해당 서버를 동적으로 구성해야 하는데 이러한 작업도 전역 Web Services 아키텍처의 일부이며 WS-Routing specification WS-Referral specification 과 일치합니다. 전역 Web Services 아키텍처가 발전함에 따라 해당 사양 및 기타 필요 사항이 소개됩니다.

더 자세한 내용은 Global XML Web Services Architecture 를 참조하십시오.

Posted by 굥쓰
Study/WebService2008. 2. 20. 01:18
Sample Download : Fritz Onion

Building a customizable Web site complete with a collection of pluggable Web Parts is fairly easy with the portal infrastructure of ASP.NET 2.0. This model is very flexible, allowing users to easily place your Web Parts anywhere on the Web page so they are free to customize your site. However, these advantages can also lead to inefficiencies that may degrade the user experience, since you may not know beforehand which components will be used together, and therefore can’t make specific data retrieval optimizations for each individual component.

The most common inefficiency in a typical portal site occurs when multiple Web Parts simultaneously make network requests for data. Each request, whether to a Web service or a remote database, ends up adding to the overall time it takes to process the page even though the requests are typically independent of each other and could conceivably be issued in parallel.

Fortunately, ASP.NET 2.0 also introduces an easy-to-use asynchronous page model that, when used in combination with asynchronous Web service calls and asynchronous database access, can significantly improve the response time for a portal page as several independent Web Parts collect data in parallel. Here I’ll look at techniques for building Web Parts that perform their data retrieval asynchronously to make the portal pages that contain them more responsive and scalable.


Web Part Congestion

Let’s begin by considering the portal page shown in Figure 1. In this sample there are four Web Parts on a portal page, each retrieving data from a different source. The full source for this sample application is available for download on the MSDN®Magazine Web site and I encourage you to review the application as you read this column. In the sample, three of the Web Parts retrieve their data from a Web service, which intentionally waits for three seconds before returning. The fourth Web Part issues an ADO.NET query to a SQL Server database, which also waits three seconds before returning. This is an exaggerated example of the problem, but it’s not all that improbable.

Figure 1 Sample Portal Page
Figure 1 Sample Portal Page

Each of the Web Parts in the sample application is built with a user control and binds the results of the data retrieval to the controls that display it. The code and markup for each control is kept to a minimum so that the example is simple and lets you focus on making the Web Parts asynchronous.

Here’s the NewsWebPart.ascx user control file:

   <%@ Control Language="C#" 
           AutoEventWireup="true" 
           CodeFile="NewsWebPart.ascx.cs"  
           Inherits="webparts_
           NewsWebPart" %>

   <asp:BulletedList ID="_newsHeadlines" 
           runat="server">
   </asp:BulletedList>
And here’s the corresponding codebehind file for the news headlines sample Web Part:
public partial class webparts_NewsWebPart : UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        PortalServices ps = new PortalServices();
        _newsHeadlines.DataSource = ps.GetNewsHeadlines();
        _newsHeadlines.DataBind();
    }
}

Note how it interacts with a Web service to retrieve the sample news headlines. The stock quotes Web Part and the weather forecaster Web Part are implemented in much the same way and use different methods of the same Web service to retrieve their data. Similarly, Figure 2 shows the SalesReportWebPart.ascx user control file and the corresponding codebehind file for the sales report sample Web Part. Note how the control uses ADO.NET to retrieve the sales data from a database and then populates a GridView control with that data.

Figure 3 Sequential Web Part Processing
Figure 3 Sequential Web Part Processing

As soon as the sample portal page is run, a problem becomes apparent. It takes more than 12 seconds for the request to process—a delay that will make most users avoid using the application. The reason for this lengthy delay is shown in Figure 3, which traces the path of execution a request takes when this page is executed. Like any other control in a page’s control hierarchy, each Web Part is loaded in turn, in the order defined by the control hierarchy of the page. Because this process is sequential, each Web Part must wait for the part preceding it in the hierarchy to complete before it can begin requesting its data and preparing its response. Because of the artificial 3-second delay introduced in each data retrieval, it is easy to see why it takes 12 seconds for the response to complete. Each Web Part is performing a completely independent data retrieval, one after the other. The important thing to realize is that all of these retrievals could be performed in parallel, cutting the response time by 75 percent. That is my goal here.

Back to top

Asynchronous Web Access

In the example, three Web Parts use Web services to retrieve their data, and one uses ADO.NET to access a database. Let’s start by making the Web service invocations asynchronous, since there is some nice support in the Web service proxy classes generated by the Web Services Description Language tool WSDL.exe (or the Visual Studio 2005 Add Web Service Reference tool) for performing Web method invocation asynchronously.

When a Web service proxy class is created in ASP.NET 2.0, it actually generates three different ways of invoking any particular method, one synchronous and two asynchronous. For example, the Web service proxy that the Web Parts are using has the following methods available for invoking the GetNewsHeadlines Web method:

   public string[] GetNewsHeadlines()
   
   public IAsyncResult BeginGetNewsHeadlines(
       AsyncCallback callback, object asyncState) 
   public string[] EndGetNewsHeadlines(       IAsyncResult asyncResult) 

   public void GetNewsHeadlinesAsync() 
   public void GetNewsHeadlinesAsync(       object userState)
   public event
       GetNewsHeadlinesCompletedEventHandler 
       GetNewsHeadlinesCompleted;

The first method, GetNewsHeadlines, is the standard synchronous method. The next two, BeginGetNewsHeadlines and EndGetNewsHeadlines, can be used to invoke the method asynchronously and can be tied into any number of asynchronous mechanisms in .NET through the standard IAsyncResult interface.

But the most interesting method to use in this scenario is the last one: GetNewsHeadlinesAsync. In order to use this particular method, I must register a delegate with the proxy class’s event that was specifically generated to capture the results of async invocations (the GetNewsHeadlinesCompleted event in the example). The delegate signature is strongly typed to contain the return values of the method so that I can easily extract the results in the method implementation.

Using this event-based asynchronous method, rewriting the Web method invocation in the headline news Web Part to be asynchronous is easy, as shown in Figure 4. I first subscribe a delegate to the GetNewsHeadlinesCompleted event of the proxy class, and then call the GetNewsHeadlinesAsync method. In the implementation of the method subscribed to the completed event, I bind the results of the Web method call to the BulletedList to display to the client. One additional consideration is that these asynchronous methods only work if the Web Part is placed on a page with the Async="true" attribute set, which can be checked programmatically by looking at the IsAsync property of the containing page. If the page the Web Part is placed on is not async, then I need to resort to standard synchronous binding, as shown in Figure 4.

Now, for the asynchronous Web Part to perform its data retrieval asynchronously, it must be placed on a page with the Async attribute set to true, so I modify the Page directive of the portal page to look like the following:

<%@ Page Language="C#" AutoEventWireup="true"  Async="true" %>

Once I update the other two Web Parts that use Web services to retrieve their data asynchronously, the portal page is much more responsive. In fact, depending on the order in which the parts are loaded, it may render to the client in just over three seconds (if the sales Web Part is loaded first, it will take just over six seconds)! Even though the sales report Web Part is still sequentially accessing the database, the other three Web Parts are now performing their Web service invocations asynchronously so the primary request thread is no longer waiting for their completion. Of course, I ultimately want to have all of the I/O-bound work be asynchronous so that clients could use both Web service and database-driven Web Parts without unnecessary sequential blocking.

The other reason to push the I/O-bound work onto asynchronous I/O requests is to relinquish the primary thread back to the thread pool to service other requests. Currently I’m relinquishing the thread only after my sales report database query has completed, which means I’m sitting idly for three full seconds occupying a threadpool thread that could be used to service other requests. If I can make this last I/O-bound request for data asynchronous as well, my page will use the request thread only long enough to spool off all the asynchronous I/O requests and then return immediately back to the pool.

Back to top

How It Works

If you’ve ever done any asynchronous programming, you probably have the feeling that the minor changes made to the Web service invocation can’t possibly be sufficient. I didn’t even have to touch an IAsyncResult interface, nor did I have to let the containing page know that I was performing asynchronous operations (by registering a task or some other technique), and yet it all seemed to work as I had hoped.

The secret lies in the Web service proxy class’s implementation of the asynchronous method, along with a helper class introduced in the Microsoft® .NET Framework 2.0 called the AsyncOperationManager. When I called the GetNewsHeadlinesAsync method of the proxy class, it mapped the call onto an internal helper method of the SoapHttpClientProtocol base class, called InvokeAsync, from which the proxy class derives. InvokeAsync does two important things—it registers the asynchronous operation by calling the AsyncOperationManager’s static CreateOperation method, and it then launches the request asynchronously using the WebRequest class’s BeginGetRequestStream method. At this point the call returns and the page goes on processing its lifecycle, but because the page has been marked with the Async="true" attribute, it will only continue processing the request up through the PreRender event and will then return the request thread to the thread pool. Once the asynchronous Web request completes, it will invoke the method I subscribed to the completed event of the proxy on a separate thread drawn from the I/O thread pool. If this is the last of the asynchronous operations to complete (kept track of by the synchronization context of the AsyncOperationManager), the page will be called back and the request will complete its processing from where it left off, starting at the PreRenderComplete event. Figure 5 shows this entire lifecycle when you use asynchronous Web requests in the context of an asynchronous page.

Figure 5 Asynchronous Web Requests in Asynchronous Pages
Figure 5 Asynchronous Web Requests in Asynchronous Pages

The AsyncOperationManager is a class that is designed to be used in different environments to help in the management of asynchronous method invocations. For example, if I called a Web service asynchronously from within a Windows® Forms application, it would also tie into the AsyncOperationManager class. The difference between each environment is the SynchronizationContext associated with the AsyncOperationManager. When running in the context of an ASP.NET-based application, the SynchronizationContext will be set to an instance of the AspNetSynchronizationContext class. The primary purpose here is to keep track of how many outstanding asynchronous requests are pending so that when they are all complete, the page request processing can resume. In contrast, when in a Windows Forms-based application, the SynchronizationContext will be set to an instance of the WindowsFormsSynchronizationContext class. Its primary purpose is to allow for easier marshaling of invocations from a background thread to the UI thread.

Back to top

Asynchronous Data Access

Now, back to the problem of making the last Web Part asynchronous and the general issue of performing asynchronous data retrieval with ADO.NET. Unfortunately, there is no equivalent to the simple asynchronous mechanism exposed by Web service proxies for performing asynchronous data retrieval, so I’m going to have to do a little more work to get the final Web Part to participate in the asynchronous shuffle. I can work with the new asynchronous methods on the SqlCommand class and the asynchronous task feature of ASP.NET. Using SqlCommand, I can now invoke commands asynchronously using one of the following methods:

  • IAsyncResult BeginExecuteReader(AsyncCallback ac, object state)
  • IAsyncResult BeginExecuteNonQuery(AsyncCallback ac, object state)
  • IAsyncResult BeginExecuteXmlReader(AsyncCallback ac, object state)
And I can invoke the corresponding completion methods once the data stream is ready to begin reading:
  • SqlDataReader EndExecuteReader(IAsyncResult ar)
  • int EndExecuteNonQuery(IAsyncResult ar)
  • XmlReader EndExecuteXmlReader(IAsyncResult ar)

To use any of these asynchronous retrieval methods, "async=true" must be added to the connection string. For this scenario, I am interested in populating a GridView by binding it to a SqlDataReader, so I will use the BeginExecuteReader method to initiate the asynchronous call.

To tie this into the asynchronous page, ASP.NET 2.0 also allows you to register asynchronous tasks that need to be executed before the page completes rendering. This is a more explicit model than the one I used with the Web service proxies, but it also provides more flexibility. To register an asynchronous task, I create an instance of the PageAsyncTask class and initialize it with three delegates: begin handler, end handler, and timeout handler. The begin handler must return an IAsyncResult interface, so this is where I will launch my asynchronous data request using BeginExecuteReader. The end handler is called once the task is complete (when there is data ready to read in this example), at which point I can use the results. ASP.NET will take care of invoking the begin handler just before it relinquishes the request thread (immediately after the PreRender event completes). Figure 6 shows the updated implementation of the sales report Web Part performing asynchronous data access using the asynchronous tasks and the asynchronous BeginExecuteReader method of the SqlCommand class.

Note that I could use this same technique with my Web service requests by using the alternate asynchronous methods provided on the proxy class (BeginGetNewsHeadlines, for example). One potential advantage to this technique is that I can also specify a timeout handler. If the remote invocations fail to return in time, the associated timeout handler will be invoked. This timeout is specified in the Page directive using the AsyncTimeout attribute and defaults to 20 seconds. Also note that unlike when using the event-based asynchronous pattern, when using Page.RegisterAsyncTask I don’t have to branch to a synchronous invocation based on the result of Page.IsAsync. Asynchronous page tasks in Web Parts work just fine on synchronous pages, and even allow for parallel execution of Web Parts. The core difference is that on synchronous pages (ones without the Async="true" attribute), the main page thread won’t be released back to the thread pool during the execution of the asynchronous operations.

With all of the Web Parts now performing their data retrieval asynchronously, I can now use these parts in any page marked as asynchronous and know that the response time will no longer be the sum of the time it takes for all the Web Parts to retrieve their data, but the maximum amount of time taken by any one Web Part. By marking the page as asynchronous and using Web Parts that perform asynchronous I/O, I also increase the potential scalability of the site since the page will free up the primary request thread to service other clients while waiting for the data. The key takeaway here is that if you’re building portal sites with ASP.NET 2.0, you should keep in mind all of the new asynchronous features introduced in this release, and take advantage of them to improve both the responsiveness and scalability of your applications. For more information on asynchronous support in ASP.NET 2.0, see Jeff Prosise’s Wicked Code column in the October 2005 issue of MSDN Magazine.

Send your questions and comments for Fritz to xtrmasp@microsoft.com.

출처 : MSDN (http://msdn.microsoft.com/msdnmag/issues/06/07/ExtremeASPNET/default.aspx)

Posted by 굥쓰
Study/MSDN2008. 2. 20. 00:33

서버 측 비동기 웹 메소드

Matt Powell
Microsoft Corporation

2002년 10월

요약: Matt Powell은 높은 성능의 Microsoft ASP.NET 웹 서비스를 생성하기 위해 서버 쪽에서 비동기 웹 메소드를 사용하는 방법을 보여줍니다.


소개

9월의 3번째 컬럼 (영문) 에서, Microsoft .NET Framework의 서버쪽 특성을 사용해서 HTTP를 통한 비동기 웹서비스 호출에 관해 썼습니다. 이 방법은 많은 백그라운드 쓰레드나 응용 프로그램에 락을 거는 것 없이도 웹 서비스를 호출하는 매우 유용한 방법입니다. 이제 우리는 서버 쪽에서 비슷한 특성을 제공하는 비동기 웹 메소드에 대해 살펴보고자 합니다. 비동기 웹 메소드는 ISAPI extension에서 사용하는 HSE_STATUS_PENDING로써 제공되는 높은 성능의 방식과 비슷하지만, 쓰레드 풀을 직접 관리하는 부담없이 관리되는 코드 안에서 동작되는 모든 이점을 누릴 수 있습니다.

첫째로 일반적인, 동기적 Microsoft ASP.NET의 웹 메소드를 살펴봅시다. 동기적인 웹 메소드에 대한 응답은 메소드로부터 반환이 되었을 때 전송됩니다. 만일 요청을 완료하기에 상대적으로 긴 시간이 걸린다면, 메소드 호출이 끝났을 때까지 요청을 처리하는 쓰레드를 사용하게 됩니다. 불행히도, 오랜 시간이 걸리는 것은 데이터베이스에 질의를 하는 경우와 같은 작업이거나, 다른 웹 서비스를 호출하는 경우입니다. 예를 들면 만일 데이터베이스 호출을 하는 경우, 현재 쓰레드는 데이터베이스에 대한 호출을 완료할 때까지 기다리게 됩니다. 현재 쓰레드는 질의로부터 응답이 올 때까지 아무것도 하지 못하고 기다리게 됩니다. 비슷한 문제들이 TCP 소켓에 대한 호출을 기다리거나 최후위의 웹 서비스를 완료할 때까지 기다리는 일이 일어납니다.

쓰레드들이 대기 중이라는 것을 좋지 않은 일입니다 - 특별히 스트레스를 받는 서버 시나리오에서는 더욱 그러합니다. 대기중인 쓰레드는 다른 요청들을 서비스하는 것과 같은, 생산적인 어떠한 것도 처리할 수 없기 때문입니다. 우리가 서버에서 오랜 시간이 걸리는 백그라운드 프로세스를 시작할 때 필요한 것, 그러나 ASP.NET 프로세스 풀로 현재 쓰레드를 반환하는 것입니다. 그리고 오랜 시간이 걸리는 백그라운드 프로세스가 종료되었을 때, 우리는 ASP.NET에 대한 요청의 완료 신청을 받고 요청에 대한 처리를 마치도록 호출된 콜백 함수를 가지는 것입니다. 이것은 ASP.NET에서 비동기 웹 메소드를 통해 제공되는 특징입니다.

어떻게 비동기 웹 메소드가 동작하는가

웹 메소드를 사용하는 전형적인 ASP.NET 웹 서비스를 우리가 작성할 때, Microsoft Visual Studio .Net은 단순하게 웹 메소드들에 대한 요청을 받았을 때 호출될 수 있는 어셈블리를 생성하도록 코드를 컴파일해 줍니다. 이 어셈블리는 SOAP에 관한 어떤 것도 알지 못합니다. 당신의 응용 프로그램이 처음 실행 될 때, ASMX 핸들러는 어느 웹 메소드가 노출되었는지를 검사하도록 어셈블리를 나타내야 합니다. 일반적인, 동기적 요청에 대해서 이것은 단순히 연관된 WebMethod 속성을 가진 어던 메소드를 발견하는 문제이고, 그리고 SOAPAction HTTP 헤더에 기초해서 올바른 메소드를 호출하도록 로직을 설정하면 됩니다.

비동기 요청에 대해서는, 비동기 적이라고 알 수 있는 특정한 종류의 시그니처를 가진 웹 메소드를 ASMX 핸들러가 리플렉션(Reflection) 동안 찾게 됩니다. 이 경우, 다음의 규칙을 따르는 한 쌍의 메소드들을 찾게 됩니다:

  • BeginXXXEndXXX의 이름을 가지는 웹 메소드가 있으며 여기서 XXX는 노출하기를 원하는 메소드의 이름을 나타냅니다.
  • BeginXXX 함수는 IAsyncResult 인터페이스를 반환하며 마지막 두 개의 입력 파라메터는 AsyncCallback과 객체를 취합니다.
  • The EndXXX 함수는 IAsyncResult인터페이스형 파라미터를 취합니다.
  • 양쪽 모두 WebMethod 속성으로 표시되어야 합니다.

만일 ASMX 핸들러가 이러한 모든 요구 조건에 맞는 2개의 메소드를 발견했다면, 일반 웹 메소드처럼 WSDL에 XXX메소드를 노출시킵니다. 입력으로써 BeginXXX에 대한 시그니처안에 AsyncCallback 파라미터에 정의된 파라미터들을 받게 되며, EndXXX함수에 의해 반환되는 것을 반환하게 됩니다. 만일 우리가 동기적인 선언형태의 웹 메소드를 가졌다면 이와 같이 보일 겁니다:

    [WebMethod]
    public string LengthyProcedure(int milliseconds) {...}

비동기적인 선언의 경우 아래와 같이 보입니다:

    [WebMethod]
    public IAsyncResult BeginLengthyProcedure(
                            int milliseconds, 
                            AsyncCallback cb, 
                            object s) {...}

    [WebMethod]
    public string EndLengthyProcedure(IAsyncResult call) {...}

각각에 대한 WSDL은 동일합니다.

ASMX핸들러가 어셈블리를 리플렉션하고 비동기적인 웹 메소드를 감지한 후에, 반드시 그 메소드에 대한 요청을 동기적인 요청과는 다르게 처리해야 합니다. 단순하게 메소드를 호출하는 것 대신에, BeginXXX 메소드를 호출합니다. 이것은 함수로 전달된 파라미터에서 입력 요청을 직렬해제(deserialize)합니다. 그러나 여기서는 BeginXXX메소드로 여분의 AsyncCallback파라메터로써 내부 콜백 함수에 대한 포인터를 전달합니다.

이러한 접근은 웹 서비스 클라이언트 응용 프로그램에서 .NET Framework에 있는 비동기적 프로그래밍 페러다임과 비슷합니다. 비동기적 웹 서비스 호출에 대한 클라이언트 지원의 경우에, 클라이언트 컴퓨터에 대한 쓰레드를 블록처리하는 것에서 자유로울 수 있으며, 반면에 서버 쪽에서도 서버에서의 쓰레드를 블록처리하지 않고 사용합니다. 여기에는 두 가지 다른 점이 있습니다. 첫 번째로 직접 BeginXXXEndXXX 함수를 호출하는 코드를 작성하는 것 대신에, ASMX핸들러가 대신 이들을 호출해 줍니다. 두 번째로, Visual Studio .NET "웹 참조 추가"마법사나 WSDL.EXE를 사용해서 생성된 코드를 사용하는 것 대신에 BeginXXXEndXXX 함수에 대한 코드를 작성할 수 있습니다. 그러나, 결과적으로 다른 어떤 작업을 수행할 수 있도록 쓰레드를 자유롭게 하는 것은 동일합니다.

ASMX핸들러가 서버의 BeginXXX 함수를 호출한 후에, 받게 되는 어느 다른 요청들을 처리할 수 있도록 프로세스의 쓰레드 풀로 쓰레드를 반환합니다. ASMX 핸들러는 BeginXXX함수로 전달된 콜백 함수가 요청에 대한 처리를 마치고 호출되기까지 대기하게 됩니다.

한번 콜백 함수가 호출되면, ASMX 핸들러는 웹 메소드가 수행하기에 필요한 어떤 처리를 완료할 수 있도록 EndXXX 함수를 호출하게 되며, 그리고 반환 데이터는 SOAP응답 안에 직렬화되도록 제공됩니다. EndXXX함수가 반환된 후 응답이 보내지며 요청에 대한 HttpContext가 해지됩니다.

단순한 비동기 웹 메소드

비동기 웹 메소드를 설명하기 위해서, 필자는 아래에 보여준 코드처럼 LengthyProcedure라고 불리는 단순한 동기 메소드부터 시작합니다. 우리는 어떻게 비동기적으로 동일한 것을 처리하는 지를 볼 것입니다. LengthyProcedure는 수천 분의 몇 초 동안만 블록킹 됩니다.

[WebService]
public class SyncWebService : System.Web.Services.WebService
{
    [WebMethod]
    public string LengthyProcedure(int milliseconds) 
    { 
        System.Threading.Thread.Sleep(milliseconds);
        return "Success"; 
    }
}

이제 우리는 LengthyProcedure를 비동기 웹 메소드로 변환합니다. 우리는 반드시 BeginLengthyProcedure 함수를 생성해야 하며 그리고 앞에서 기술한 것처럼 EndLengthyProcedure 함수를 생성해야 합니다. 우리의 BeginLengthyProcedureIAsyncResult 인터페이스를 반환해야 함을 기억해야 합니다. 이 경우에 필자는 우리의 BeginLengthyProcedure가 위임자(delegate)를 사용하는 비동기적 메소드 호출을 하도록 하며 그 위임자에 대해 BeginInvoke 메소드를 갖도록 하려 합니다. 콜백 함수는 우리의 델리게이트에서 BeginInvoke 메소드를 통해 처리될 수 있도록 BeginLengthyProcedure로 전달되며, BeginInvoke로부터 반환되는 IAsyncResultBeginLenthyProcedure 메소드에서 반환됩니다.

EndLengthyProcedure는 우리의 델리게이트가 완료될 때 호출됩니다. 우리는 IAsyncResult에서 전달된 델리게이트에서 EndInvoke메소드를 호출하며 EndLenghyProcedure호출에 대한 입력으로써 받게 됩니다. 반환된 문자열은 우리의 웹 메소드로부터 반환된 문자열이 됩니다. 여기에 코드가 있습니다:

[WebService]
public class AsyncWebService : System.Web.Services.WebService
{
    public delegate string LengthyProcedureAsyncStub(
        int milliseconds);

    public string LengthyProcedure(int milliseconds) 
    { 
        System.Threading.Thread.Sleep(milliseconds);
        return "Success"; 
    }

    public class MyState 
    { 
        public object previousState; 
        public LengthyProcedureAsyncStub asyncStub; 
    }

    [ System.Web.Services.WebMethod ]
    public IAsyncResult BeginLengthyProcedure(int milliseconds, 
        AsyncCallback cb, object s)
    {
        LengthyProcedureAsyncStub stub 
            = new LengthyProcedureAsyncStub(LengthyProcedure);
        MyState ms = new MyState();
        ms.previousState = s; 
        ms.asyncStub = stub;
        return stub.BeginInvoke(milliseconds, cb, ms);
    }
  
    [ System.Web.Services.WebMethod ]
    public string EndLengthyProcedure(IAsyncResult call)
    {
        MyState ms = (MyState)call.AsyncState;
        return ms.asyncStub.EndInvoke(call);
    }
}

비동기 웹 메소드가 사용될 때

응용 프로그램에 비동기적 웹 메소드를 사용할지를 결정해야 할 때 고려해야 할 몇 가지 문제가 있습니다. 첫 번째로, 호출에 대해 BeginXXX 함수는 IAsyncResult 인터페이스를 반환해야 합니다. IAsyncResult는 스트림, Microsoft Windows Sockets호출, 파일 I/O를 실행, 다른 하드웨어 디바이스와 교류, 비동기적 메소드의 호출, 그리고 물론 다른 웹 서비스를 호출하는 것과 같은 다양한 비동기적 I/O작업들로부터 반환됩니다. 이러한 종류의 비동기적 작업들 중의 하나로부터 IAsyncResult를 얻기를 원한다면, BeginXXX 함수로부터 반환할 수 있습니다. 다른 옵션은 IAsyncResult 인터페이스를 구현하는 고유의 클래스를 생성하는 것인데, 앞에서 언급한 I/O 구현중의 하나를 포장하는 것 이상을 해야만 합니다.

우리가 언급했던 비동기적 작업들 중 대부분의 경우, 백그라운드 비동기적 호출을 감싸는 비동기적 웹 메소드의 사용을 주의 깊게 만들고 더 효율적인 웹 서비스 코드로 결과를 주게 됩니다. 위임자를 사용하는 비동기적 메소드 호출을 사용할 경우 예외가 있습니다. 위임자는 프로세스의 쓰레드 풀에 있는 쓰레드를 실행해서 비동기적 메소드 호출을 하도록 합니다. 불행히도, 이러한 것들은 입력된 요청들을 서비스하기 위해 ASMX 핸들러에서 사용된 것과 동일한 쓰레드여야 합니다. 하드웨어나 네트워킹 리소스에 대한 실제 I/O 오퍼레이션을 수행하는 호출들과는 달리, 위임자를 사용하는 비동기적 메소드 호출은 실행 중에 프로세스 쓰레드 중의 하나를 여전히 블록킹하게 됩니다. 원래 쓰레드를 블록킹하고 동기적으로 실행되는 웹 메소드를 갖도록 해야 합니다.

다음의 예제는 최후위 웹 서비스를 호출하는 비동기적 웹 메소드를 보여줍니다. 비동기적으로 실행되는 WebMethod 속성으로 BeginGetAgeEndGetAge 메소드를 표시합니다. 이러한 비동기적 웹 메소드에 대한 코드는 반환될 정보를 얻기 위해 UserInfoQuery라 불리는 최후위 웹 메소드를 호출합니다. UserInfoQuery에 대한 호출은 비동기적으로 실행되며 BeginGetAge 메소드로 전달되는 AsyncCallback 함수를 전달합니다. 이것은 최후위 요청이 완료될 때 호출되는 내부 콜백 함수가 실행되도록 합니다. 콜백 함수는 요청을 완료하도록 EndGetAge 메소드를 호출합니다. 이 경우 코드는 앞에서의 예제보다 더 간단해지며, 그리고 우리의 중간 계층의 웹 메소드 요청을 서비스하는 동일한 쓰레드 풀에서 최후위 프로세스을 실행하지 않는 추가적인 이점을 가지게 됩니다.

[WebService]
public class GetMyInfo : System.Web.Services.WebService
{
    [WebMethod]
    public IAsyncResult BeginGetAge(AsyncCallback cb, Object state)
    {
        // 비동기적인 웹 서비스 호출
        localhost.UserInfoQuery proxy 
            = new localhost.UserInfoQuery();
        return proxy.BeginGetUserInfo("User's Name", 
                                      cb, 
                                      proxy);
    }

    [WebMethod]
    public int EndGetAge(IAsyncResult res)
    {
        localhost.UserInfoQuery proxy 
            = (localhost.UserInfoQuery)res.AsyncState;
        int age = proxy.EndGetUserInfo(res).age;
        //  웹 서비스 호출로부터의 결과에 추가적인 프로세싱을 수행합니다. 
        return age;
    }
}

웹 메소드 내부에서 일어나는 가장 공통적인 형태의 I/O작업중의 하는 SQL 데이터베이스에 대한 호출입니다. 불행히도, Microsoft ADO.NET은 2002년 현재 좋은 비동기적인 호출 메카니즘을 가지고 있지 않으며, 단순하게 비동기적인 위임자 호출 안에서 SQL호출을 감싸는 것은 효율성 부분에서 도움이 되지 않습니다. 웹 서비스로 데이터베이스를 노출시키기를 원한다면 Microsoft SQL Server 2000 Web Services Toolkit (영문)을 사용을 고려해 볼 수 있습니다. 데이터베이스를 업데이트하거나 질의하는 비동기적인 웹 서비스를 호출하기 위해서 .NET Framework의 지원을 받을 수 있습니다.

웹 서비스 호출을 통해 SQL을 액세스하는 것은 많은 최후위 리소스들에 사용해야 할 방법 중 하나입니다. 만일 유닉스 서버와 커뮤니케이션을 하기 위해 TCP소켓을 사용한다면, 또는 적절한 데이터베이스 드라이버들을 통해 다른 SQL플랫폼중의 어던 것을 액세스한다면 - 또는 DCOM을 사용해서 액세스하는 리소스를 가지고 있다면 -웹 서비스로써 리소스를 노출시켜주는 현 시점에서 가능한 다양한 웹 서비스 툴킷들의 사용을 고려해 볼 수 있습니다.

이러한 접근법을 취하는 것의 장점 중의 하나는, .NET Framework에서 비동기적 웹 서비스 호출과 같은 클라이언트 쪽 웹 서비스 인프라구조에서의 장점들을 취하는 것입니다. 비용이 들지 않는 비동기적 호출을 얻는다면, 클라이언트 액세스 메커니즘은 비동기적 웹 메소드와 함께 효율적으로 작업할 수 있게 될 겁니다.

비 동기적 웹 메소드를 사용해서 데이터를 집계

오늘날의 많은 웹 서비스들은 최후위 쪽에 있는 다중의 리소스들을 액세스하며 최전위 웹 서비스에 대한 정보들을 집계합니다. 심지어 웹 메소드 모델을 더욱 복잡하게 만드는 여러 개의 최후위 리소스들을 호출하는 것은, 상당한 양의 효율성을 얻게 됩니다.

서비스 A와 서비스 B인, 두 개의 최후위 웹 서비스를 호출하는 웹 메소드가 있다고 합시다. BeginXXX 함수로부터, 비동기적으로 서비스 A를 호출하고 서비스 B를 비동기적으로 호출합니다. 당신은 이러한 비동기적 호출의 각각에 대해서 고유의 콜백 함수를 전달할 수 있습니다. 서비스 A와 서비스 B 양쪽에서 결과를 받은 후에 웹 메소드의 완료를 자동 동작시키기 위해, 당신이 제공한 콜백 함수는 양쪽의 요청이 완료되었는지를 검사하며, 반환된 데이터에 대한 처리를 실행하며, 그리고 BeginXXX 함수에 대해 전달된 콜백 함수를 호출하게 됩니다. 이것은 비동기적 웹 메소드가 완료되도록 반환되는, EndXXX 함수를 호출하도록 자동 동작시킵니다.

결론

비동기적인 웹 메소드는 프로세스 쓰레스 풀 안에 귀중한 쓰레드가 아무것도 하지 않으면서 블록되도록 두지 않고 ASP.NET 웹 서비스에서 최후위 서비스를 호출하는 효율적인 메커니즘을 제공합니다. 최후위 리소스들에 대한 비동기적인 요청들과의 결합하여, 서버는 그들의 웹 메소드들을 최대로 처리할 수 있게 됩니다. 고성능의 웹 서비스 응용 프로그램을 개발하기 위해서는 이러한 접근방법을 고려해 보아야 합니다.


저자에 대해

Matt Powell 은 SOAP Toolkit 1.0을 개발을 도운, MSDN 구조화 샘플 팀의 멤버입입니다. Matt는 MMicrosoft출판사의 Running Microsoft Internet Information Server 의 저자미여, 많은 잡지 기사들을 집필했습니다.

출처 : MSDN - http://ms.inposti.com/korea/msdn/library/ko-kr/dnservice/html/service10012002.aspx

Posted by 굥쓰