Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Other Async Function Features

In this section, I’d like to share with you some additional features related to async functions. Microsoft Visual Studio has great support for debugging async functions. When the debugger is stopped on an await operator, stepping over (F10) will actually break into the debugger when the next statement is reached after the operation completes. This code might even execute on a different thread than the one that initiated the operation! This is incredibly useful and simplifies debugging substantially.

Also, if you accidentally step into (F11), an async function, you can step out (Shift+F11) of the func- tion to get back to the caller; you must do this while on the opening brace of the async function. Af- ter you pass the open brace, step out (Shift+F11) won’t break until the async function runs all the way to completion. If you need to debug the calling method before the state machine runs to completion, put a breakpoint in the calling method and just run (F5) to it.

Some asynchronous operations execute quickly and therefore complete almost instantaneously. When this happens, it is inefficient to suspend the state machine and then have another thread im- mediately resume the state machine; it is much more efficient to have the state machine just continue its execution. Fortunately, the await operator’s compiler-generated code does check for this. If an asynchronous operation completes just before the thread would return, the thread does not return and instead, it just executes the next line of code.

This is all fine and good but occasionally, you might have an async function that performs an intensive amount of processing before initiating an asynchronous operation. If you invoke the func- tion via your app’s GUI thread, your user interface will become non-responsive to the user. And, if the asynchronous operation completes synchronously, then your user-interface will be non-responsive even longer. So, if you want to initiate an async function from a thread other than the thread that calls it, you can use Task’s static Run method as follows.

 

// Task.Run is called on the GUI thread Task.Run(async () => {

// This code runs on a thread pool thread

// TODO: Do intensive compute­bound processing here...

 

await XxxAsync(); // Initiate asynchronous operation

// Do more processing here...

});


This code demonstrates another C# feature: async lambda expressions. You see, you can’t just put an await operator inside the body of a regular lambda expression, because the compiler wouldn’t know how to turn the method into a state machine. But placing async just before the lambda expres- sion causes the compiler to turn the lambda expression into a state machine method that returns a Task or Task<TResult>, which can then be assigned to any Func delegate variable whose return type is Task or Task<TResult>.

When writing code, it is very easy to invoke an async function, forgetting to use the await opera- tor; the following code demonstrates.



 

static async Task OuterAsyncFunction() {

InnerAsyncFunction(); // Oops, forgot to put the await operator on this line!

 

// Code here continues to execute while InnerAsyncFunction also continues to execute...

}

 

static async Task InnerAsyncFunction() { /* Code in here not important */ }

 

Fortunately, when you do this, the C# compiler issues the following warning: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

This is nice but on rare occasions, you actually don’t care when InnerAsyncFunction completes,

and you do want to write the preceding code and not have the compiler issue a warning.

 

To quiet the compiler warning, you can simply assign the Task returned from InnerAsync­

Function to a variable and then ignore the variable.9

 

static async Task OuterAsyncFunction() {

var noWarning = InnerAsyncFunction(); // I intend not to put the await operator on this line.

 

// Code here continues to execute while InnerAsyncFunction also continues to execute...

}

 

Or, I prefer to define an extension method that looks like this.

 

[MethodImpl(MethodImplOptions.AggressiveInlining)] // Causes compiler to optimize the call away public static void NoWarning(this Task task) { /* No code goes in here */ }

 

And then I can use it like this.

 

static async Task OuterAsyncFunction() {

InnerAsyncFunction().NoWarning(); // I intend not to put the await operator on this line.

 

// Code here continues to execute while InnerAsyncFunction also continues to execute...

}

 

One of the truly great features of performing asynchronous I/O operations is that you can initiate many of them concurrently so that they are all executing in parallel. This can give your application

 

 

 
 

9 Fortunately, the compiler does not give a warning about the local variable never being used.


a phenomenal performance boost. I never showed you the code that started my named pipe server and then made a bunch of client requests to it. Let me show that code now.

 

public static async Task Go() {

// Start the server, which returns immediately because

// it asynchronously waits for client requests

StartServer(); // This returns void, so compiler warning to deal with

 

// Make lots of async client requests; save each client's Task<String> List<Task<String>> requests = new List<Task<String>>(10000);

for (Int32 n = 0; n < requests.Capacity; n++) requests.Add(IssueClientRequestAsync("localhost", "Request #" + n));

 

// Asynchronously wait until all client requests have completed

// NOTE: If 1+ tasks throws, WhenAll rethrows the last­throw exception String[] responses = await Task.WhenAll(requests);

 

// Process all the responses

for (Int32 n = 0; n < responses.Length; n++) Console.WriteLine(responses[n]);

}

 

This code starts the named pipe server so that it is listening for client requests and then, in a for loop, it initiates 10,000 client requests as fast as it possibly can. Each time IssueClientRequest­ Async is called, it returns a Task<String> object, which I then add to a collection. Now, the named pipe server is processing these client requests as fast as it possibly can using thread pool threads that will try to keep all the CPUs on the machine busy.10 As the server completes processing each request; each request’s Task<String> object completes with the string response returned from the server.

In the preceding code, I want to wait until all the client requests have gotten their response before processing their results. I accomplish this by calling Task’s static WhenAll method. Internally, this method creates a Task<String[]> object that completes after all of the List’s Task objects have completed. I then await the Task<String[]> object so that the state machine continues execu- tion after all of the tasks have completed. After all the tasks have completed, I loop through all the responses at once and process them (call Console.WriteLine).

Perhaps you’d prefer to process each response as it happens rather than waiting for all of them to complete. Accomplishing this is almost as easy by way of Task’s static WhenAny method. The revised code looks like this.

 

public static async Task Go() {

// Start the server, which returns immediately because

// it asynchronously waits for client requests StartServer();

 

// Make lots of async client requests; save each client's Task<String> List<Task<String>> requests = new List<Task<String>>(10000);

 

 
 

10 Fun observation: when I tested this code on my machine, the CPU usage on my 8-processor machine went all the way up to 100 percent, of course. Because all the CPUs were busy, the machine got hotter and the fan got a lot louder! After processing completed, the CPU usage went down and the fan got quieter. Fan volume is a new way of verifying that everything is working as it should.


for (Int32 n = 0; n < requests.Capacity; n++) requests.Add(IssueClientRequestAsync("localhost", "Request #" + n));

 

// Continue AS EACH task completes while (requests.Count > 0) {

// Process each completed response sequentially Task<String> response = await Task.WhenAny(requests);

requests.Remove(response); // Remove the completed task from the collection

 

// Process a single client's response Console.WriteLine(response.Result);

}

}

 

Here, I create a while loop that iterates once per client request. Inside the loop I await Task’s WhenAny method, which returns one Task<String> object at a time, indicating a client request that has been responded to by the server. After I get this Task<String> object, I remove it from the col- lection, and then I query its result in order to process it (pass it to Console.WriteLine).

 

 


Date: 2016-03-03; view: 904


<== previous page | next page ==>
Nbsp;   Async Functions in the Framework Class Library | Nbsp;   Applications and Their Threading Models
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.007 sec.)