Section 7.3. Asynchronous Call Programming Models


7.3. Asynchronous Call Programming Models

To support asynchronous invocation, multiple threads are required. However, it would be a waste of system resources and a performance penalty if .NET spun off a new thread for every asynchronous method invocation. A better approach is to use a pool of already created worker threads. .NET has just such a pool, called the .NET thread pool. One of the nice things about the .NET way of supporting asynchronous calls is that it hides this interaction completely. As previously indicated, there are quite a few programming models available for dealing with asynchronous calls. This section examines the various options: blocking, waiting, polling, and completion callbacks. In general, BeginInvoke( ) initiates an asynchronous method invocation. The calling client is blocked for only the briefest momentthe time it takes to queue up a request for a thread from the thread pool to execute the methodand then control returns to the client. EndInvoke( ) manages method completion; specifically, retrieving output parameters and return values, and error handling.

7.3.1. Using BeginInvoke( ) and EndInvoke( )

The compiler-generated BeginInvoke( ) and EndInvoke( ) methods take this form:

     public virtual IAsyncResult BeginInvoke(<input and input/output parameters>,                                             AsyncCallback callback,                                             object asyncState);     public virtual <return value> EndInvoke(<output and input/output parameters>,                                             IAsyncResult asyncResult);

BeginInvoke( ) accepts the input parameters of the original signature the delegate defines. Input parameters include both value types passed by value or by reference (using the out or ref modifiers) and reference types. The original method's return values and any explicit output parameters are part of the EndInvoke( ) method. For example, for this delegate definition:

     public delegate string MyDelegate(int number1,out int number2,ref int number3,object obj);

the corresponding BeginInvoke( ) and EndInvoke( ) methods look like this:

     public virtual IAsyncResult BeginInvoke(int number1,out int number2,ref int                                             number3,object obj,                                             AsyncCallback callback,object asyncState);     public virtual string EndInvoke(out int number2,ref int number3,                                     IAsyncResult asyncResult);

BeginInvoke( ) accepts two additional input parameters, not present in the original delegate signature: AsyncCallback callback and object asyncState. The callback parameter is actually a delegate object representing a reference to a callback method that receives the method-completed notification event. asyncState is a generic object that passes in whatever state information is needed by the party handling the method completion. These two parameters are optional: the caller can choose to pass in null instead of either one of them. For example, to asynchronously invoke the Add( ) method of the Calculator class, if you have no interest in the result and no interest in a callback method or state information, you would write:

     Calculator calculator = new Calculator(  );     BinaryOperation oppDel = calculator.Add;     oppDel.BeginInvoke(2,3,null,null);

The object itself is unaware that the client is using a delegate to asynchronously invoke the method. The same object code handles both the synchronous and asynchronous invocation cases. As a result, every .NET class supports asynchronous invocation. Note that the classes should still comply with certain design guidelines described in this chapter, even though the compiler will compile them if they don't.

Because delegates can be used on both instance and static methods, clients can use BeginInvoke( ) to asynchronously call static methods as well. The remaining question is, how would you get the results of the method?

7.3.1.1 The IAsyncResult interface

Every BeginInvoke( ) method returns an object implementing the IAsyncResult interface, defined as:

     public interface IAsyncResult     {         object AsyncState{get;}         WaitHandle AsyncWaitHandle{get;}         bool CompletedSynchronously{get;}         bool IsCompleted{get;}     }

You will see a few uses for the properties of IAsyncResult later. For now, it's sufficient to know that the returned IAsyncResult object uniquely identifies the method that was invoked using BeginInvoke( ). You can pass the IAsyncResult object to EndInvoke( ) to identify the specific asynchronous method execution from which you wish to retrieve the results. Example 7-1 shows the entire sequence.

Example 7-1. Simple asynchronous execution sequence
 Calculator calculator = new Calculator(  ); BinaryOperation oppDel = calculator.Add; IAsyncResult asyncResult1 = oppDel.BeginInvoke(2,3,null,null); IAsyncResult asyncResult2 = oppDel.BeginInvoke(4,5,null,null); /* Do some work */ int result; result = oppDel.EndInvoke(asyncResult1); Debug.Assert(result == 5); result = oppDel.EndInvoke(asyncResult2); Debug.Assert(result == 9);

As simple as Example 7-1 is, it does demonstrate a few key points. The most important of these is that because the primary use of EndInvoke( ) is to retrieve any output parameters as well as the method's return value, EndInvoke( ) blocks its caller until the method it's waiting for (identified by the IAsyncResult object passed in) returns. The second point is that the same delegate object (with exactly one target method) can invoke multiple asynchronous calls on the target method. The caller can distinguish between the different pending calls using each unique IAsyncResult object returned from BeginInvoke( ). In fact, when the caller makes asynchronous calls, as in Example 7-1, the caller must save the IAsyncResult objects. In addition, the caller should make no assumption about the order in which the pending calls complete. Remember: the asynchronous calls are carried out on threads from the thread pool, and because of thread context switches (as well as internal pool management and bookkeeping), it's quite possible that the second call will complete before the first one.

There are other uses for the IAsyncResult object besides passing it to EndInvoke( ): you can use it to get the state object parameter of BeginInvoke( ), you can wait for the method completion, and you can get the original delegate used to invoke the call. You will see how to do all that later. Although they aren't evident in Example 7-1, there are three important programming points you must always remember when using delegate-based asynchronous calls:

  • EndInvoke( ) can be called only once for each asynchronous operation. Trying to call it more than once results in an exception of type InvalidOperationException.

  • Although in general the compiler-generated delegate class can manage multiple targets, when you use asynchronous calls the delegate is only allowed to have exactly one target method in its internal list. Calling BeginInvoke( ) when the delegate's list contains more than one target will result in an ArgumentException being thrown, reporting that the delegate must have only one target.

  • You can pass the IAsyncResult object to EndInvoke( ) only on the same delegate object that was used to dispatch the call. Passing the IAsyncResult object to a different delegate results in an exception of type InvalidOperationException, stating "The IAsyncResult object provided doesn't match this delegate." Example 7-2 demonstrates this point. It results in the exception, even though the other delegate targets the same method.

Example 7-2. Pass the IAsyncResult object only to the same delegate that invoked the call
 Calculator calculator = new Calculator(  ); BinaryOperation oppDel1 = calculator.Add; BinaryOperation oppDel2 = calculator.Add; IAsyncResult asyncResult = oppDel1.BeginInvoke(2,3,null,null); //This will result in an InvalidOperationException oppDel2.EndInvoke(asyncResult);

To emphasize that the delegate can have only one target method in its list when used to invoke an asynchronous call, I recommend using the = operator to add the target method to the delegate list:

     Binary Operation oppDel;     oppDel = calculator.Add;

although the += operator works just as well:

     Binary Operation oppDel = null;     oppDel += calculator.Add;

An advantage to using the = operator is that you don't need to initialize the delegate object before assigning it.


7.3.1.2 The AsyncResult class

Often, one client initiates an asynchronous call, but another calls EndInvoke( ). Even when only one client is involved, it's likely to call BeginInvoke( ) in one code section (or method) and EndInvoke( ) in another. It's bad enough that you have to either save the IAsyncResult object or pass it to another client. It would be even worse if you had to do the same for the delegate object that invokes the asynchronous call, just because you needed that delegate to call EndInvoke( ). Fortunately, an easier solution is available, because the IAsyncResult object itself carries with it the delegate that created it. When BeginInvoke( ) returns the IAsyncResult reference, it's actually an instance of a class called AsyncResult, defined as:

     public class AsyncResult : IAsyncResult, IMessageSink     {        //IAsyncResult implementation        public virtual object AsyncState{get;}        public virtual WaitHandle AsyncWaitHandle{get;}        public virtual bool CompletedSynchronously{get;}        public virtual bool IsCompleted{get;}        //Other properties        public bool EndInvokeCalled{get; set;}        public virtual object AsyncDelegate{get;}        /* IMessageSink implementation */     }

AsyncResult is found in the System.Runtime.Remoting.Messaging namespace. AsyncResult has a property called AsyncDelegate, which, as you might guess, is a reference to the original delegate that dispatches the call. Example 7-3 shows how to use the AsyncDelegate property to call EndInvoke( ) on the original delegate.

Example 7-3. Using the AsyncDelegate property of AsyncResult to access the original delegate
 using System.Runtime.Remoting.Messaging; public class CalculatorClient {    IAsyncResult m_AsyncResult;    public void AsyncAdd(  )    {       Calculator calculator = new Calculator(  );       DispatchAdd(calculator,2,3);       /* Do some work */       int result = GetResult(  );       Debug.Assert(result == 5);    }    void DispatchAdd(Calculator calculator,int number1,int number2)    {       BinaryOperation oppDel = calculator.Add;       m_AsyncResult = oppDel.BeginInvoke(2,3,null,null);    }    int GetResult(  )    {       int result = 0;       //Obtain original delegate       AsyncResult asyncResult = (AsyncResult) m_AsyncResult;       BinaryOperation oppDel  = (BinaryOperation) asyncResult.AsyncDelegate;       Debug.Assert(asyncResult.EndInvokeCalled == false);       result = oppDel.EndInvoke(m_AsyncResult);       return result;    } }

Note that because the AsyncDelegate property is of type object, you need to downcast it to the actual delegate type (BinaryOperation in Example 7-3).

Example 7-3 demonstrates using another useful property of AsyncResultthe Boolean EndInvokeCalled property. You can use it to verify that EndInvoke( ) hasn't been called:

     Debug.Assert(asyncResult.EndInvokeCalled == false);

7.3.1.3 Polling or waiting for completion

In the programming model described in the previous section, when a client calls EndInvoke( ), the client is blocked until the asynchronous method returns. This may be fine if the client has a finite amount of work to do while the call is in progress, and if, once that work is done, the client can't continue its execution without the returned value or the output parameters of the method (or even just the knowledge that the method call has completed). However, what if the client only wants to

: Asynchronous COM

Windows 2000 introduced asynchronous COM and the async_uuid IDL interface attribute. This attribute caused the MIDL compiler to generate two interface definitions: one for synchronous calls and one for asynchronous calls. For every method on the normal synchronous interface, the asynchronous interface (named AsyncI<interface name>) had a method called Begin_<method name>, which dispatched the call asynchronously to the corresponding method. The Begin_<method name> method had only the input parameters of the original method. The asynchronous interface also had a matching method called Finish_<method name> for every method on the original interface; this method retrieved the output parameters and blocked the method completion. Asynchronous COM also supported a notification mechanism to signal the client upon method completion, as an alternative to polling with Finish_<method name>.

MIDL implemented asynchronous COM by generating a custom marshaling proxy and stub, which used threads from the RPC thread pool to dispatch the call. The major advantage of asynchronous COM was that the same component code could be used both synchronously and asynchronously, so developers didn't have to waste time developing their own asynchronous method invocation mechanisms. The main disadvantages were that it was an esoteric mechanism (the majority of COM developers were not even aware it existed), it was supported only on Windows 2000 and above, it had limitations on parameter types, and it was difficult to use. However, the abstract principles of the mechanism and its design pattern (i.e., that of using a tool to generate a standard solution for asynchronous support and using the same component code in both cases) were sound, and they might well have been the inspiration for the .NET architects.


check if the method execution is completed? Or what if the client wants to wait for completion for a fixed timeout, do some additional finite processing, and then wait again? .NET supports these alternative programming models.

The IAsyncResult interface returned from BeginInvoke( ) has the AsyncWaitHandle property, of type WaitHandle. WaitHandle is actually a .NET wrapper around a native Windows waitable event handle. WaitHandle has a few overloaded wait methods. For example, the WaitOne( ) method returns only when the handle is signaled. Example 7-4 demonstrates using WaitOne( ).

Example 7-4. Using IAsyncResult.AsyncWaitHandle to block until method completion
 Calculator calculator = new Calculator(  ); BinaryOperation oppDel = calculator.Add; IAsyncResult asyncResult = oppDel.BeginInvoke(2,3,null,null); /* Do some work */ asyncResult.AsyncWaitHandle.WaitOne(  ); //This may block int result; result = oppDel.EndInvoke(asyncResult); //This will not block Debug.Assert(result == 5);

Logically, Example 7-4 is identical to Example 7-1, which called only EndInvoke( ). If the method is still executing when WaitOne( ) is called, it will block. If, however, the method execution is complete, WaitOne( ) will not block and the client will proceed to call EndInvoke( ) for the returned value. The important difference between Example 7-4 and Example 7-1 is that the call to EndInvoke( ) in Example 7-4 is guaranteed not to block its caller.

Example 7-5 demonstrates a more practical way of using WaitOne( ), by specifying a timeout (10 milliseconds in this example). When you specify a timeout, WaitOne( ) returns when the method execution is completed or when the timeout has elapsed, whichever condition is met first.

Example 7-5. Using IAsyncResult.AsyncWaitHandle to specify the wait timeout
 Calculator calculator = new Calculator(  ); BinaryOperation oppDel = calculator.Add; IAsyncResult asyncResult = oppDel.BeginInvoke(2,3,null,null); while(asyncResult.IsCompleted == false) {    asyncResult.AsyncWaitHandle.WaitOne(10,false); //This may block    /* Do some work */ } int result; result = oppDel.EndInvoke(asyncResult); //This will not block

When you specify a timeout, WaitOne( ) also accepts a Boolean flag, whose meaning is discussed in Chapter 8. You can ignore that flag for now, as it bears no relevance to this discussion.


Example 7-5 also uses another handy property of IAsyncResult, called IsCompleted. IsCompleted lets you find the status of the call without waiting or blocking. You can even use IsCompleted in a strict polling mode:

     while(asyncResult.IsCompleted == false)     {        /* Do some work */     }

This, of course, has all the adverse effects of polling (e.g., consuming CPU power for nothing), so you should generally avoid using IsCompleted this way.

The AsyncWaitHandle property really shines when you use it to manage multiple concurrent asynchronous method calls in progress. You can use the WaitHandle class's static WaitAll( ) method to wait for completion of multiple asynchronous methods, as shown in Example 7-6.

Example 7-6. Waiting for completion of multiple methods
 Calculator calculator = new Calculator(  ); BinaryOperation oppDel1 = calculator.Add; BinaryOperation oppDel2 = calculator.Add; IAsyncResult asyncResult1 = oppDel1.BeginInvoke(2,3,null,null); IAsyncResult asyncResult2 = oppDel2.BeginInvoke(4,5,null,null); WaitHandle[] handleArray = {asyncResult1.AsyncWaitHandle,asyncResult2. AsyncWaitHandle}; WaitHandle.WaitAll(handleArray); int result; //These calls to EndInvoke(  ) will not block result = oppDel1.EndInvoke(asyncResult1); Debug.Assert(result == 5); result = oppDel2.EndInvoke(asyncResult2); Debug.Assert(result == 9);

To use WaitAll( ), you need to construct an array of handles. Note also that you still need to call EndInvoke( ) to access the returned values.

Instead of waiting for all the methods to return, you can choose to wait for any of them to return. To do so, use the WaitAny( ) static method of the WaitHandle class:

     WaitHandle.WaitAny(handleArray);

Like WaitOne( ), both WaitAll( ) and WaitAny( ) have a few overloaded versions that let you specify a timeout period to wait instead of waiting indefinitely.

7.3.2. Using Completion Callback Methods

As an alternative to the previous options for managing asynchronous calls (blocking, waiting, or polling), .NET offers another programming model altogether: callbacks. The idea is simple: the client provides .NET with a method and requests that .NET call that method back when the asynchronous method completes. The client can provide a callback instance method or static method and have the same callback method handle completion of multiple asynchronous methods. The only requirement is that the callback method have the following signature:

     <visibility modifier> void <Name>(IAsyncResult asyncResult);

The convention for a callback method name is to prefix it with On<>for example, OnAsyncCallBack( ), OnMethodCompletion( ), and so on. Here is how the callback mechanism works. As explained previously, .NET uses a thread from the thread pool to execute the method dispatched via BeginInvoke( ). When the asynchronous method execution is completed, instead of quietly returning to the pool, the worker thread calls the callback method.

To use a callback method, the client needs to provide BeginInvoke( ) with a delegate that targets the callback method. That delegate is provided as the penultimate parameter to BeginInvoke( ) and is always of type AsyncCallback. AsyncCallback is a .NET-provided delegate from the System namespace, defined as:

     public delegate void AsyncCallback(IAsyncResult asyncResult);

Example 7-7 demonstrates asynchronous call management using a completion callback method.

Example 7-7. Managing asynchronous completion using a callback method
 public class CalculatorClient {    public void AsyncAdd(  )    {       Calculator calculator = new Calculator(  );       BinaryOperation oppDel = calculator.Add;       oppDel.BeginInvoke(2,3,OnMethodCompletion,null);    }    void OnMethodCompletion(IAsyncResult asyncResult)    {       int result = 0;       AsyncResult resultObj = (AsyncResult)asyncResult;       Debug.Assert(resultObj.EndInvokeCalled == false);       BinaryOperation oppDel  = (BinaryOperation)resultObj.AsyncDelegate;       result = oppDel.EndInvoke(asyncResult);       Trace.WriteLine("Operation returned " + result);    } }

When providing the completion callback method to BeginInvoke( ), you can rely on delegate inference and pass the method name directly, as shown in Example 7-7.

Unlike with the previous programming models described in this chapter, when you use a completion callback method, there's no need to save the IAsyncResult object returned from BeginInvoke( )when .NET calls the callback method, it provides the IAsyncResult object as a parameter. Note in Example 7-7 the use of a downcast of the IAsyncResult parameter to an AsyncResult class to get the original delegate that dispatched the call. You need that delegate to call EndInvoke( ). Because .NET provides a unique IAsyncResult object for each asynchronous method, you can channel multiple asynchronous method completions to the same callback method:

     Calculator calculator = new Calculator(  );     BinaryOperation oppDel1 = calculator.Add;     BinaryOperation oppDel2 = calculator.Add;     oppDel1.BeginInvoke(2,3,OnMethodCompletion,null);     oppDel2.BeginInvoke(4,5,OnMethodCompletion,null);

Completion callback methods are by far the preferred model in any event-driven application. An event-driven application has methods that trigger events (or dispatch requests and post and process messages) and methods that handle these requests and fire their own events as a result. Writing an application as event-driven makes it easier to manage multiple threads, events, and messages and allows for greater scalability, responsiveness, and performance. .NET asynchronous call management using completion callback methods fits into such an architecture like a hand in a glove. The other options (waiting, blocking, and polling) are available for applications that are strict, predictable, and deterministic in their execution flow. I recommend that you use a completion callback method whenever possible.

7.3.2.1 Callback methods and thread safety

Because the callback method is executed on a thread from the thread pool, you must provide for thread safety in the callback method and in the object that provides it. This means you must use synchronization objects or locks to access the object's member variables. You need to worry about synchronizing between the "normal" thread of the object and the worker thread from the pool, and, potentially, synchronization between multiple worker threads all calling concurrently into the callback method to handle their respective asynchronous method completion. The callback method must be reentrant and thread-safe. Thread safety and synchronization are covered in the next chapter.

7.3.2.2 Passing state information

I have ignored the last parameter to BeginInvoke( ), object asyncState, up until now, when its use can be best appreciated. The asyncState object, known as a state object in .NET, is provided as a generic container for whatever need you deem fit. The party handling the method completion can access such a container object via the object AsyncState property of IAsyncResult. Although you can certainly use state objects with any of the other .NET asynchronous call programming models (blocking, waiting, or polling), they are most useful in conjunction with completion methods. The reason is simple: in all the other programming models, it's up to you to manage the IAsyncResult object, and managing an additional container isn't that much of an added liability. When you are using a completion callback the container object offers the only way to pass in additional parameters to the callback method, whose signature is predetermined by .NET.

Example 7-8 demonstrates how you might use a state object to pass an integer value as an additional parameter to the completion callback method. Note that the callback method must downcast the AsyncState property to the actual type that Debug.Assert( ) expects.

Example 7-8. Passing an additional parameter using a state object
 public class CalculatorClient {    public void AsyncAdd(  )    {       Calculator calculator = new Calculator(  );       BinaryOperation oppDel = calculator.Add;       int asyncState = 4;       oppDel.BeginInvoke(2,3,OnMethodCompletion,asyncState);    }    void OnMethodCompletion(IAsyncResult asyncResult)    {       int asyncState;       asyncState = (int) asyncResult.AsyncState;       Debug.Assert(asyncState == 4);       /* Rest of the callback */    } }

7.3.3. Performing Asynchronous Operations Without Delegates

Delegate-based asynchronous calls like those described in the preceding sections let you asynchronously invoke any method, on any class. This technique provides valuable flexibility to a client, but it requires that you define a delegate with a signature that matches the method you want to invoke. Certain operations, such as disk or network accesses, web requests, web service calls, committing transactions or message queuing, may be long in duration or even open-ended in their very nature. In such cases, you will usually opt to invoke the operations asynchronously. The designers of the .NET Framework sought to ease the task of performing such operations by building into the classes that offer them Begin<Operation> and End<Operation> methods. These methods always take a form identical to the BeginInvoke( ) and EndInvoke( ) methods provided by a delegate class:

     public <return type> <Operation>(<parameters>);     IAsyncResult Begin<Operation>(<input and input/output parameters>,                                                AsyncCallback callback,                                                    object asyncState);     public <return type> End<Operation>(<output and input/output parameters >                                                    IAsyncResult asyncResult);

For example, the abstract class Stream, defined in the System.IO namespace, provides asynchronous Read( ) and Write( ) operations:

     public abstract class Stream : MarshalByRefObject,IDisposable     {        public virtual int Read(byte[]buffer,int offset,int count);        public virtual IAsyncResult BeginRead(byte[]buffer,int offset,int count,                                              AsyncCallback callback,object state);        public virtual int EndRead(IAsyncResult asyncResult);        public virtual void Write(byte[]buffer,int offset,int count);        public virtual IAsyncResult BeginWrite(byte[]buffer,int offset,int count,                                               AsyncCallback callback,object state);        public virtual void EndWrite(IAsyncResult asyncResult);        /* Other methods and properties */     }

The Stream class is the base class for all other stream classes, such as FileStream, MemoryStream, and NetworkStream. All the Stream-derived classes override these methods and provide their own implementations.

Example 7-9 demonstrates an asynchronous read operation on a FileStream object. Note the passing of the useAsync parameter to the FileStream constructor, indicating asynchronous operations on the stream.

Example 7-9. Asynchronous stream read with a completion callback
 public class FileStreamClient {    Byte[] m_Array = new Byte[2000];    public void AsyncRead(  )    {       bool useAsync = true;       Stream stream = new FileStream("MyFile.bin",FileMode.Open,FileAccess.Read,                                                      FileShare.None,1000,useAsync);       using(stream)       {          stream.BeginRead(m_Array,0,10,OnMethodCompletion,null);       }    }    void OnMethodCompletion(IAsyncResult asyncResult)    {       bool useAsync = true;       Stream stream = new FileStream("MyFile.bin",FileMode.Open,FileAccess.Read,                                                      FileShare.None,1000,useAsync);       using(stream)       {          int bytesRead = stream.EndRead(asyncResult);       }       //Access m_Array    } }

Another example of a class that provides its own asynchronous methods is the web service proxy class that you can generate using the WSDL.exe comand-line utility. Imagine that the Calculator class in the following code snippet exposes its methods, such as Add( ), as web services:

     using System.Web.Services;     public class Calculator     {        [WebMethod]        public int Add(int argument1,int argument2)        {           return argument1 + argument2;        }        //Other methods     }

The proxy class WSDL.exe auto-generates for the client will contain BeginAdd( ) and EndAdd( ) methods that invoke the web service asynchronously:

     using System.Web.Services.Protocols;     public partial class Calculator : SoapHttpClientProtocol     {        public int Add(int argument1,int argument2)        {...}        public IAsyncResult BeginAdd(int argument1,int argument2                                     AsyncCallback callback,object asyncState)        {...}        public int EndAdd(IAsyncResult asyncResult)        {...}        /* Other members */     }

Using non-delegate-based asynchronous method calls is similar to using the BeginInvoke( ) and EndInvoke( ) methods provided by a delegate class: you dispatch the asynchronous operation using Begin<Operation>, and you can call End<Operation> to block until completion, wait for the operation (or multiple operations) to complete, or use a callback method. However, there is no uniform requirement to call End<Operation> on the original object that dispatched the Begin<Operation> call. With some classes (such as web service proxy classes or Stream-derived classes), you can create a new object and call End<Operation> on it. Example 7-10 demonstrates this technique when using a web service proxy class.

Example 7-10. Asynchronous web-service call with a completion callback
 public class CalculatorWebServiceClient {    public void AsyncAdd(  )    {       //Calculator is the WSDL.exe-generated proxy class       Calculator calculator = new Calculator(  );       calculator.BeginAdd(2,3,OnMethodCompletion,null);    }    void OnMethodCompletion(IAsyncResult asyncResult)    {       //Calculator is the WSDL.exe-generated proxy class       Calculator calculator = new Calculator(  );       int result;       result = calculator.EndAdd(asyncResult);       Trace.WriteLine("Operation returned " + result);    } }

The web service proxy class generated by WSDL.exe offers another mechanism for asynchronous invocation, discussed in Chapter 8. When adding a web reference using Visual Studio 2005, it will only offer that other mechanism in the proxy class.




Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net