2014-11-25 7 views
6

Ho usato un po 'la codifica asincrona, ma non capisco davvero come usarlo, anche se capisco il concetto e perché ne ho bisogno.Vuoi capire async

Ecco il mio set up:

Ho un API Web che chiamerò dalla mia applicazione ASP.NET MVC e il mio Web API chiamerà DocumentDB. Negli esempi di codice, vedo molte parole chiave attese durante l'invio di query a DocumentDB.

Sono confuso se devo rendere il mio metodo di azione Indice nella mia app MVC asincrona? Sono confuso anche se il mio metodo CreateEmployee() nella mia API Web dovrebbe essere asincrono?

Qual è il modo corretto di utilizzare async in questo scenario?

Ecco il mio codice (Questo codice è attualmente mi dà errori perché il mio metodo di azione MVC non è asincrona) ---- ASP.NET MVC Codice App ----

public ActionResult Index() 
{ 

    Employee emp = new Employee(); 
    emp.FirstName = "John"; 
    emp.LastName = "Doe"; 
    emp.Gender = "M"; 
    emp.Ssn = "123-45-6789"; 

    using (var client = new HttpClient()) 
    { 
     client.BaseAddress = new Uri("http://myWebApi.com"); 
     client.DefaultRequestHeaders.Accept.Clear(); 
     client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 

     HttpResponseMessage response = await client.PostAsJsonAsync("hr/create/newemployee", emp); 
     if (response.IsSuccessStatusCode) 
     { 
     emp = await response.Content.ReadAsAsync<Employee>(); 
     } 
    } 

    // Display employee info 
    return View(emp); 
} 

---- Web API Codice ----

private static readonly string endPointUrl = ConfigurationManager.AppSettings["EndPointUrl"]; 
private static readonly string authorizationKey = ConfigurationManager.AppSettings["AuthorizationKey"]; 
private static readonly string databaseId = ConfigurationManager.AppSettings["DatabaseId"]; 
private static DocumentClient client; 

public static async Task<Employee> CreateEmployee(Employee emp) 
{ 
    try 
    { 
     //Create a Document client 
     using (client = new DocumentClient(new Uri(endPointUrl), authorizationKey)) 
     { 
     //Get the database 
     var database = await GetDatabaseAsync(); 

     //Get the Document Collection 
     var collection = await GetCollectionAsync(database.SelfLink, "Employees"); 

     await client.CreateDocumentAsync(collection.SelfLink, emp); 

     // Further process employee 
     } 
    } 
    catch 
    { 
     // Handle error 
    } 

    return employee; 
} 

private static async Task<DocumentCollection> GetCollectionAsync(string dbLink, string id) 
{ 
    DocumentCollection collection = client.CreateDocumentCollectionQuery(dbLink).Where(c => c.Id == id).ToArray().FirstOrDefault(); 

    return collection; 
} 

private static async Task<Database> GetDatabaseAsync() 
{ 
    Database database = client.CreateDatabaseQuery().Where(db => db.Id == databaseId).ToArray().FirstOrDefault(); 

    return database; 
} 
+0

Nel tuo caso, stai liberando alcune risorse quando dici attendi. Tuttavia, puoi migliorare utilizzando tasks.whenall - leggi questo: http://msdn.microsoft.com/en-AU/library/hh556530.aspx – codebased

risposta

6

è possibile utilizzare solo await all'interno di un metodo se questo metodo è async e async metodi devono restituire Task, Task<T> o void anche se void ritorno async metodi sono riservati per i gestori di eventi, perché le eccezioni gettate al loro interno vengono ingoiate e non è possibile eseguire il completamento o le successive attività della catena.

Credo che la vostra azione Index deve essere async e restituire un tuo metodo Task<ActionResult> e CreateEmployee ha bisogno di essere async così come si sta usando await al suo interno.

Vedi Best Practices in Asynchronous Programming per alcune linee guida su quando e come utilizzare async-await

1
async await 

Essi sono difficili da capire.

Prima di tutto, nei tuoi metodi in Web API, stai utilizzando async senza attendere. Sono sicuro che stai ricevendo degli errori/avvertenze lì giusto?

-

asincrona attendono vengono utilizzati per riportare il filo di lavoro al chiamante quando si è in attesa di I/O per essere finito. Quindi, sì, lo si vuole usare sia nel lato MVC che in quello Web API. Assicurati di aver compreso questa frase prima di proseguire.

-

La cosa su async/attendere è che, se lo si utilizza, è necessario utilizzare tutto il percorso attraverso le funzioni di chiamata, altrimenti non ha senso (e ti ricevi anche errori/avvertenze). Ciò significa che qualsiasi libreria che si sta utilizzando deve supportarla. In questo caso "DocumentClient". Per convenzione, i metodi che lo supportano terminano in "Async" e restituiranno un'attività che è possibile attendere.

-

Quindi la tua risposta breve: uso asincrono attendono fin dall'inizio (il controller), e cercare di farlo attendere qualunque tempo le operazioni che chiama. Se questo è anche il tuo codice, dovresti essere in grado di aspettare da lì ... e attendere da lì ... fino a quando finalmente non chiami qualcosa che non è il tuo codice. Se puoi aspettare quel codice che non è tuo, allora sei pronto. Se non puoi, allora non dovresti usare async attendi fin dall'inizio.

(nessun modo questo aveva un senso)

5

Ecco la mia spiegazione

class MainClass 
{ 
    public static async Task<String> AsyncMethod(int delay) { 

     await Task.Delay (TimeSpan.FromSeconds(delay)); 

     return "The method has finished it's execution after waiting for " + delay + " seconds"; 
    } 

    public static async Task Approach1(int delay) 
    { 
     var response = await AsyncMethod (delay); // await just unwraps Task's result 

     Console.WriteLine (response); 
    } 

    public static Task Approach2(int delay) 
    { 
     return AsyncMethod(delay).ContinueWith(message => Console.WriteLine(message)); // you could do the same with 
    } 

    public static void Main (string[] args) 
    { 
     var operation1 = Approach1 (3); 
     var operation2 = Approach2 (5); 

     Task.WaitAll (operation1, operation2); 

     Console.WriteLine("All operations are completed") 
    } 
} 

Alla fine sono entrambi Approach1 e Approach2 pezzi identici di codice.

Lo async/await è zucchero sintattico attorno all'API attività. Prende il tuo metodo async dividerlo in parti prima dello await e dopo lo await. La parte "prima" viene eseguita immediatamente. La parte "dopo" viene eseguita quando l'operazione await è completata. È possibile tenere traccia della seconda parte dell'operazione tramite l'API delle attività poiché si ottiene un riferimento a un'attività.

In generale async consente di trattare una chiamata di metodo come una sorta di operazione lunga a cui è possibile fare riferimento tramite l'API Attività e attendere fino al termine e continuare con un'altra parte di codice. O tramite la chiamata ContinueWith di via usando await in generale è lo stesso.

Prima async/await/Task concetti persone sono state utilizzando callback, ma la gestione degli errori è stato facile come l'inferno, il Task è simile a un concetto di callback tranne che è in grado consentire la gestione delle eccezioni più facilmente.

In generale tutto questo Task/async/attendono mantra è vicino al concetto di promises se è successo che hai lavorato con jQuery/Javascript c'è un concetto simile Ecco una bella domanda che spiega come si fa lì "jQuery deferreds and promises - .then() vs .done()"


Edit: ho appena scoperto che .NET manca attuazione then funzionalità simile a quella che si trova in jQuery/Javascript.

La differenza tra ContinueWith e Then è che Then è in grado di comporre compito, ed eseguire sequenzialmente mentre ContinueWith non è, è solo in grado di lanciare compito in parallelo, ma può essere facilmente implementato tramite il costrutto attendono. Ecco il mio codice aggiornato contenente l'intero shebang:

static class Extensions 
{ 
    // Implementation to jQuery-like `then` function in .NET 
    // According to: http://blogs.msdn.com/b/pfxteam/archive/2012/08/15/implementing-then-with-await.aspx 
    // Further reading: http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx 
    public static async Task Then(this Task task, Func<Task> continuation) 
    { 
     await task; 
     await continuation(); 
    } 

    public static async Task<TNewResult> Then<TNewResult>( 
     this Task task, Func<Task<TNewResult>> continuation) 
    { 
     await task; 
     return await continuation(); 
    } 

    public static async Task Then<TResult>( 
     this Task<TResult> task, Func<TResult,Task> continuation) 
    { 
     await continuation(await task); 
    } 

    public static async Task<TNewResult> Then<TResult, TNewResult>( 
     this Task<TResult> task, Func<TResult, Task<TNewResult>> continuation) 
    { 
     return await continuation(await task); 
    } 
} 

class MainClass 
{ 
    public static async Task<String> AsyncMethod1(int delay) { 

     await Task.Delay (TimeSpan.FromSeconds(delay)); 

     return "The method has finished it's execution after waiting for " + delay + " seconds"; 
    } 

    public static Task<String> AsyncMethod2(int delay) 
    { 
     return Task.Delay (TimeSpan.FromSeconds (delay)).ContinueWith ((x) => "The method has finished it's execution after waiting for " + delay + " seconds"); 
    } 

    public static async Task<String> Approach1(int delay) 
    { 
     var response = await AsyncMethod1 (delay); // await just unwraps Task's result 

     return "Here is the result of AsyncMethod1 operation: '" + response + "'"; 
    } 

    public static Task<String> Approach2(int delay) 
    { 
     return AsyncMethod2(delay).ContinueWith(message => "Here is the result of AsyncMethod2 operation: '" + message.Result + "'"); 
    } 

    public static void Main (string[] args) 
    { 
     // You have long running operations that doesn't block current thread 
     var operation1 = Approach1 (3); // So as soon as the code hits "await" the method will exit and you will have a "operation1" assigned with a task that finishes as soon as delay is finished 
     var operation2 = Approach2 (5); // The same way you initiate the second long-running operation. The method also returns as soon as it hits "await" 

     // You can create chains of operations: 
     var operation3 = operation1.ContinueWith(operation1Task=>Console.WriteLine("Operation 3 has received the following input from operation 1: '" + operation1Task.Result + "'")); 
     var operation4 = operation2.ContinueWith(operation2Task=>Console.WriteLine("Operation 4 has received the following input from operation 2: '" + operation2Task.Result + "'")); 

     var operation5 = Task.WhenAll (operation3, operation4) 
      .Then(()=>Task.Delay (TimeSpan.FromSeconds (7))) 
      .ContinueWith((task)=>Console.WriteLine("After operation3 and 4 have finished, I've waited for additional seven seconds, then retuned this message")); 

     Task.WaitAll (operation1, operation2); // This call will block current thread; 

     operation3.Wait(); // This call will block current thread; 
     operation4.Wait(); // This call will block current thread; 
     operation5.Wait(); // This call will block current thread; 

     Console.WriteLine ("All operations are completed"); 
    } 
}