2014-11-25 27 views
6

Użyłem trochę kodowania asynchronicznego, ale nie do końca rozumiem, jak go używać - chociaż rozumiem koncepcję i dlaczego tego potrzebuję.Chcesz zrozumieć async

Oto mój zestaw up:

Mam Web API że wezwę z mojej aplikacji ASP.NET MVC i mojego Web API wezwie DocumentDB. W przykładach kodu widzę wiele oczekujących słów kluczowych podczas wysyłania zapytań do DocumentDB.

Jestem zdezorientowany, jeśli muszę zrobić moją metodę akcji indeksu w mojej asynchronicznej aplikacji MVC? Jestem również zdezorientowany, jeśli moja metoda CreateEmployee() w moim Web API powinna być asynchroniczna?

Jaki jest właściwy sposób korzystania z asynchronizacji w tym scenariuszu?

Oto mój kod (Kod ten jest obecnie daje mi błędy, ponieważ moja metoda działania MVC nie jest asynchroniczny) ---- ASP.NET MVC Code 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); 
} 

---- Kod Web API ----

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

W twoim przypadku uwalniasz niektóre zasoby, gdy mówisz, że oczekujesz. Jednak można poprawić za pomocą tasks.whenall - przeczytaj to: http://msdn.microsoft.com/en-AU/library/hh556530.aspx – codebased

Odpowiedz

6

można używać tylko await wewnątrz metody, jeśli ta metoda jest async i async metody muszą powrócić Task, Task<T> lub void chociaż void powracającego async metody są zarezerwowane dla obsługi zdarzeń, ponieważ wyjątki wyrzucone w nich są połknięte i nie można ich zakończyć ani wykonać kolejnych zadań.

Myślę, że akcja Index musi być async i zwracają Task<ActionResult> i metoda CreateEmployee musi być async jak to jest przy użyciu await wewnątrz niego.

Zobacz Best Practices in Asynchronous Programming dla niektórych wytycznych dotyczących tego, kiedy i jak używać async-await

1
async await 

są trudne do zrozumienia.

Przede wszystkim w swoich metodach w Web API używasz asynchronicznie bez czekania. Na pewno dostaniesz jakieś błędy/ostrzeżenie, prawda?

-

asynchroniczny czekają służą do powrotu nić roboczą z powrotem do osoby dzwoniącej, gdy czekasz na I/O za zakończoną. Tak, tak, chcesz go używać zarówno w MVC i Web API stronie. Upewnij się, że rozumiesz to zdanie, zanim przejdziesz dalej.

-

Rzecz o async/czekają na to, że jeśli go używać, trzeba z niego korzystać przez całą drogę do funkcji telefonicznych, albo to nie ma sensu (i będziesz dostaję także błędy/ostrzeżenie). Oznacza to, że każda biblioteka, z której korzystasz, musi ją obsługiwać. W tym przypadku "DocumentClient". Zgodnie z konwencją metody, które ją obsługują, zakończą się "Asynchronizacją" i zwrócą zadanie, na które możesz czekać.

-

Więc krótka odpowiedź: stosowanie asynchronicznych czekają od samego początku (kontroler), a następnie spróbuj zrobić to czekać na cokolwiek długo operacje wywołuje. Jeśli to również jest twój kod, powinieneś być w stanie zaczekać ... i czekać od tego ... aż wreszcie zadzwonisz do czegoś, co nie jest twoim kodem. Jeśli możesz poczekać na kod, który nie jest twój, to jesteś ustawiony. Jeśli nie możesz, nie powinieneś używać async od samego początku.

(żaden sposób to miało sens)

5

Oto moje wyjaśnienie

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") 
    } 
} 

Ostatecznie zarówno Approach1 i Approach2 są identyczne fragmenty kodu.

Jest to cukier syntaktyczny wokół Task API. Metoda async rozdziela ją na części przed await i po await. Część "przed" jest natychmiast wykonywana. Część "po" jest wykonywana po zakończeniu operacji await. Jesteś w stanie śledzić drugą część operacji za pomocą interfejsu API zadań, ponieważ otrzymujesz odwołanie do zadania.

Ogólnie rzecz biorąc async pozwala traktować wywołanie metody jako pewnego rodzaju długą operację, do której można się odwoływać za pomocą interfejsu API zadań, i poczekać, aż zostanie zakończone, i kontynuować kolejny fragment kodu. Za pośrednictwem połączenia ContinueWith generalnie jest tak samo.

Przed async/await/Task koncepcje ludzie używali wywołania zwrotne, ale obsługa błędów było tak łatwe, jak cholera, Task jest podobny do koncepcji callback poza tym, że jest w stanie umożliwić obsługę wyjątków łatwiej.

W ogóle cały ten Zadanie/asynchroniczny/czekają mantra jest blisko koncepcji promises jeśli to się stało, że już pracował z jQuery/JavaScript istnieje podobna koncepcja Oto miły pytanie wyjaśniając, jak to się robi tam „jQuery deferreds and promises - .then() vs .done()


Edit: właśnie dowiedziałem się, że .NET brakuje realizację then funkcjonalności podobnej do znalezionego w jQuery/JavaScript.

Różnica między ContinueWith i Then że Then jest w stanie tworzyć zadania i wykonać je kolejno podczas ContinueWith nie jest, to jest tylko w stanie uruchomić zadania równolegle, ale może być łatwo realizowane poprzez konstruktu czekają. Oto mój zaktualizowany kod zawierający cały kod:

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"); 
    } 
}