Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   C#’s Asynchronous Functions

Performing asynchronous operations is the key to building scalable and responsive applications that allow you to use very few threads to execute lots of operations. And when coupled with the thread pool, asynchronous operations allow you to take advantage of all of the CPUs that are in the machine. Realizing the enormous potential here, Microsoft designed a programming model that would make it easy for developers to take advantage of this capability.3 This pattern leverages Tasks (as discussed in Chapter 27, “Compute-Bound Asynchronous Operations”) and a C# language feature called asynchro- nous functions (or async functions, for short). Here is an example of code that uses an async function to issue two asynchronous I/O operations.

 

private static async Task<String> IssueClientRequestAsync(String serverName, String message) { using (var pipe = new NamedPipeClientStream(serverName, "PipeName", PipeDirection.InOut,

PipeOptions.Asynchronous | PipeOptions.WriteThrough)) {

 

pipe.Connect(); // Must Connect before setting ReadMode pipe.ReadMode = PipeTransmissionMode.Message;

 

// Asynchronously send data to the server

Byte[] request = Encoding.UTF8.GetBytes(message); await pipe.WriteAsync(request, 0, request.Length);

 

 

 
 

3 For developers using a version of the Microsoft .NET Framework prior to version 4.5, my AsyncEnumerator class (that is part of my Power Threading library available on http://Wintellect.com/ ) allows you to use a programming model quite similar to the programming model that now ships as part of .NET Framework 4.5. In fact, the success of my AsyncEnumerator class allowed me to assist Microsoft in designing the programming model I explain in this chapter. Due to the similarities, it is trivial to migrate code using my AsyncEnumerator class to the new programming model.


// Asynchronously read the server's response Byte[] response = new Byte[1000];

Int32 bytesRead = await pipe.ReadAsync(response, 0, response.Length); return Encoding.UTF8.GetString(response, 0, bytesRead);

} // Close the pipe

}

 

In the preceding code, you can tell that IssueClientRequestAsync is an async function, be- cause I specified async on the first line just after static. When you mark a method as async, the compiler basically transforms your method’s code into a type that implements a state machine (the details of which will be discussed in the next section). This allows a thread to execute some code in the state machine and then return without having the method execute all the way to completion. So, when a thread calls IssueClientRequestAsync, the thread constructs a NamedPipeClient­ Stream, calls Connect, sets its ReadMode property, converts the passed-in message to a Byte[] and then calls WriteAsync. WriteAsync internally allocates a Task object and returns it back to IssueClientRequestAsync. At this point, the C# await operator effectively calls ContinueWith on the Task object passing in the method that resumes the state machine and then, the thread re- turns from IssueClientRequestAsync.



Sometime in the future, the network device driver will complete writing the data to the pipe and then, a thread pool thread will notify the Task object, which will then activate the Continue­ With callback method, causing a thread to resume the state machine. More specifically, a thread will re-enter the IssueClientRequestAsync method but at the point of the await operator. Our method now executes compiler-generated code that queries the status of the Task object. If the

operation failed, an exception representing the failure is thrown. If the operation completes success- fully, the await operator returns the result. In this case, WriteAsync returns a Task instead of a Task<TResult>, so there is no return value.

Now, our method continues executing by allocating a Byte[] and then calls NamedPipeClient­ Stream’s asynchronous ReadAsync method. Internally, ReadAsync creates a Task<Int32> object and returns it. Again, the await operator effectively calls ContinueWith on the Task<Int32> object passing in the method that resumes the state machine. And then, the thread returns from Issue­ ClientRequestAsync again.

Sometime in the future, the server will send a response back to the client machine, the network device driver gets this response, and a thread pool thread notifies the Task<Int32> object, which will then resume the state machine. The await operator causes the compiler to generate code that queries the Task object’s Result property (an Int32) and assigns the result to the bytesRead local variable or throws an exception if the operation failed. Then, the rest of the code in IssueClient­ RequestAsync executes, returning the result string and closing the pipe. At this point, the state machine has run to completion and the garbage collector will reclaim any memory it needed.

Because async functions return before their state machine has executed all the way to comple- tion, the method calling IssueClientRequestAsync will continue its execution right after Issue­ ClientRequestAsync executes its first await operator. But, how can the caller know when Issue­ ClientRequestAsync has completed executing its state machine in its entirety? Well, when you mark a method as async, the compiler automatically generates code that creates a Task object when the state machine begins its execution; this Task object is completed automatically when the state


machine runs to completion. You’ll notice that the IssueClientRequestAsync method’s return type is a Task<String>. It actually returns the Task<String> object that the compiler-generated code creates back to its caller, and the Task’s Result property is of type String in this case. Near the bottom of IssueClientRequestAsync, I return a string. This causes the compiler-generated code to complete the Task<String> object it created and set its Result property to the returned string.

You should be aware of the following restrictions related to async functions:

 

■ You cannot turn your application’s Main method into an async function. In addition, construc- tors, property accessor methods and event accessor methods cannot be turned into async functions.

■ You cannot have any out or ref parameters on an async function.

■ You cannot use the await operator inside a catch, finally, or unsafe block.

■ You cannot take a lock that supports thread ownership or recursion before an await opera- tor and release it after the await operator. The reason is because one thread might execute the code before the await and a different thread might execute the code after the await. If you use await within a C# lock statement, the compiler issues an error. If you explicitly call Monitor’s Enter and Exit methods instead, then the code will compile but Monitor.Exit will throw a SynchronizationLockException at run time.4

■ Within a query expression, the await operator may only be used within the first collection

expression of the initial from clause or within the collection expression of a join clause.

 

These restrictions are pretty minor. If you violate one, the compiler will let you know, and you can

usually work around the problem with some small code modifications.

 


Date: 2016-03-03; view: 1193


<== previous page | next page ==>
Nbsp;   How Windows Performs I/O Operations | Nbsp;   How the Compiler Transforms an Async Function into a State Machine
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.007 sec.)