2013-07-07 12 views
5

Mam niektóre kodu, który tworzy zadanie, które wykonuje jakąś powolną pracę tak:Jak utworzyć zadanie, które oczekuje czekać wewnątrz ciała, które zachowuje się tak samo jak wersja synchroniczna, gdy jest wywoływana funkcja Czekaj?

public static Task wait1() 
{ 
    return new Task(() => 
    { 
     Console.WriteLine("Waiting..."); 
     Thread.Sleep(10000); 
     Console.WriteLine("Done!"); 
    }); 
} 

w rzeczywistych realizacji, Thread.Sleep rzeczywiście będzie wezwanie serwis internetowy. Chciałbym zmienić ciało metody można użyć czekać (więc nie zużywają wątku podczas dostępu do sieci/uśpienia). Moja pierwsza próba (oparta na debugowaniu przez debugowanie błędów kompilacji) była następująca:

public static Task wait2() 
{ 
    return new Task(async() => 
    { 
     Console.WriteLine("Waiting..."); 
     await Task.Delay(10000); 
     Console.WriteLine("Done!"); 
    }); 
} 

Jednak; to zadanie nie wydaje się zachowywać tak samo jak pierwsze, ponieważ kiedy wywołuję na nim funkcję .Wait(); wraca natychmiast.

Poniżej znajduje się pełna próbka (aplikacja konsolowa) pokazująca różnice (aplikacja zakończy się natychmiast po uruchomieniu drugiego zadania).

Co muszę zrobić, abym mógł zadzwonić Start i czekać na Zadanie, które zdarzyło się, że ma kod z wyczekiwaniem w środku? Zadania są kolejkowane i wykonywane później przez agenta, dlatego ważne jest, aby zadanie nie zostało uruchomione automatycznie.

class Program 
{ 
    static void Main(string[] args) 
    { 
     var w1 = wait1(); 
     w1.Start(); 
     w1.Wait(); // This waits 110 seconds 

     var w2 = wait2(); 
     w2.Start(); 
     w2.Wait(); // This returns immediately 
    } 

    public static Task wait1() 
    { 
     return new Task(() => 
     { 
      Console.WriteLine("Waiting..."); 
      Thread.Sleep(10000); 
      Console.WriteLine("Done!"); 
     }); 
    } 

    public static Task wait2() 
    { 
     return new Task(async() => 
     { 
      Console.WriteLine("Waiting..."); 
      await Task.Delay(10000); 
      Console.WriteLine("Done!"); 
     }); 
    } 
} 
+2

Po co robić "nowe zadanie" w ogóle? Dlaczego nie uczynić zewnętrznej metody 'async' i czy automatycznie zwróci' Task'? Oznacza to, że 'static async Task wait3() {Console.WriteLine (" ... "); poczekaj na Task.Delay (10000); Console.WriteLine ("..."); } ' –

+0

@EricLippert Próbowałem tego; ale wygląda na to, że zachowuje się inaczej ... Wywołanie '.Start()' wyrzuca 'System.InvalidOperationException: Start może nie zostać wywołany w zadaniu stylu obietnicy.". Moje zadania idą do kolejki, którą później można nazwać; więc muszę móc je później uruchomić. –

+0

Czy Twoim celem jest zatrzymanie wait1/2 na linii oczekującej do momentu, gdy zostanie wywołany Start? Pytam to na podstawie twojego komentarza do @cucaesta, że ​​"muszę mieć możliwość zadzwonienia do Startu później". Jeśli tak jest, myślę, że pytanie wymaga aktualizacji wraz z wykonaniem, na które masz nadzieję. –

Odpowiedz

8

Wygląda na to, że nie jest to możliwe! Zobacz alexm's answer here:

Zadania zwróconych przez metod asynchronicznych są zawsze gorącą to znaczy są one tworzone w Running stan.

:-(

Pracowałem już obejść ten problem poprzez mojego agenta kolejce Func<Task>s zamiast, a przeciążenie, który otrzymuje zadanie po prostu kolejek () => task.Następnie; gdy jest on kolejce zadanie, to sprawdzić, czy to nie działa, a jeśli tak, uruchom go:

var currentTask = currentTaskFunction(); 
if (currentTask.Status == TaskStatus.Created) 
    currentTask.Start(); 

Wydaje się trochę niezgrabne, aby to zrobić (jeśli jest to proste obejście działa, dlaczego oryginalny ograniczenie metody asynchroniczne są zawsze tworzone na gorąco?), ale wydaje się, że działa to dla mnie :-)

+2

Właściwie to właśnie chcesz zrobić.W świecie 'asynchronicznym'' Task' już działa, więc jeśli chcesz * uruchomić * zadanie na w późniejszym czasie, właściwym typem jest 'Func '. –

+0

@StephenCleary Właśnie znalazłem twój blog, który to robi i jest prawie dokładną kopią tego, co pisałem! http://blog.stephencleary.com/ 2012/12/return-early-from-aspnet-requests.html Chociaż mój agent siedzi w pętli przetwarzanie kolejki jeden na raz! –

+0

Widzę, że byłoby mylące, jeśli asynchroniczne metody zwróciły zadania, które nie były Zaczęło się, szkoda, że ​​sprawia, że ​​nie jest oczywiste, jak używać czekać w dela yed zadań. –

2

można napisać to jako:

public static async Task Wait2() 
{ 
    Console.WriteLine("Waiting..."); 
    await Task.Delay(10000); 
    Console.WriteLine("Done!"); 
} 

W ogóle, to rzadko jest dobry pomysł, aby zawsze używać new Task lub new Task<T>. Jeśli musisz uruchomić zadanie za pomocą ThreadPool zamiast używać obsługi języków async/await, aby skomponować je, powinieneś użyć Task.Run, aby uruchomić zadanie. To zaplanuje uruchomienie zadania (co jest ważne, zadania powinny zawsze być "gorące" według konwencji).

Pamiętaj, że zrobienie tego spowoduje, że nie będziesz musiał dzwonić pod numer Task.Start.

+0

Celowo nazywam "Start". W mojej aplikacji zadania są "umieszczane w kolejce" i przetwarzane później.Chciałbym móc czekać, aby zwolnić wątki, ponieważ prawie każde zadanie w kolejce będzie miało połączenie z serwisem WWW w środku; używanie oczekujących byłoby fajne :-) –

0

Spróbuj tego:

public async static Task wait2() 
{ 
     Console.WriteLine("Waiting..."); 
     await Task.Delay(2000); 
     Console.WriteLine("Done!"); 
} 

Ale mamy świadomość, że to zadanie jest już uruchomiony, dzięki czemu nie trzeba zadzwonić start:

var w2 = wait2(); 
//w2.Start(); 
w2.Wait(); 

Myślę, że problem z funkcją wait2 jest to, że tworzy 2 zadania, jeden w new Task(...), a drugi w Task.Delay(). Czekasz na pierwszą, ale nie czekasz na wewnętrzną.

+0

Muszę być w stanie zadzwonić do 'Start' później, więc to nie zadziała :(Podejrzewam, że masz rację co do wielu zadań, ale wydaje mi się to mylące, że zewnętrzne zadanie nie obejmuje całego bloku pracy :( –

1

Aby to zrozumieć, zdaj sobie sprawę, że asynchronizacja/oczekiwanie zasadniczo nie tworzy nowego wątku, ale raczej harmonogramuje ten fragment kodu, który ma być pobiegł do dostępnego punktu w czasie.

Podczas tworzenia nowego zadania (async() => ...) masz zadanie, które uruchamia metodę asynchroniczną. Kiedy ta wewnętrzna metoda asynchroniczna czeka na Ciebie, "nowe zadanie" jest uważane za zakończone, ponieważ reszta została zaplanowana. Aby lepiej zrozumieć, umieść kod (dużo, jeśli chcesz) w "nowym zadaniu" przed oczekiwaniem na polecenie. To wszystko zostanie wykonane, zanim aplikacja się zakończy i gdy tylko zaczekasz, zadanie zostanie uwieńczone. Następnie wraca i kończy działanie aplikacji.

Najlepszym sposobem na uniknięcie tego jest nie umieszczanie żadnych zadań ani asynchronicznych metod wewnątrz zadania.

Usuń słowo kluczowe asynch i oczekiwane słowo kluczowe z metody i działa zgodnie z oczekiwaniami.

Jest to to samo, co tworzenie wywołania zwrotnego, jeśli jesteś o tym zaznajomiony.

void MethodAsync(Action callback) 
{ 
    //...some code 
    callback?.Invoke(); 
} 

//using this looks like this. 
MethodAsync(() => { /*code to run when complete */}); 

// To jest taka sama jak

Task MethodAsync() 
{ 
    //... some code here 
} 

//using it 

await MethodAsync(); 
/*code to run when complete */ 

rzeczą do zrozumienia jest to, że podczas tworzenia nowego zadania w ramach zadania w zasadzie. Wewnętrzne "wywołanie zwrotne" powstaje przy oczekiwaniu na słowo kluczowe.

jesteś kod wygląda następująco ..

void MethodAsync(Action callback) 
{ 
    //some code to run 
    callback?.Invoke(); // <- this is the await keyword 
    //more code to run.. which happens after we run whoever is 
    //waiting on callback 
} 

Jest Kod brakuje oczywiście. Jeśli to nie ma sensu, prosimy o kontakt ze mną, a ja pomogę. async/await (ma na celu uproszczenie) jest bestią, która najpierw owinę ci głowę. Potem dostaniesz to, to prawdopodobnie będzie twoja ulubiona rzecz w C# od linq. : P