Await ve ContinueWith arasındaki fark


119

Eğer birisi açıklayabilir awaitve ContinueWithaşağıdaki örnekte eşanlamlı veya değildirler. TPL'yi ilk kez kullanmaya çalışıyorum ve tüm belgeleri okudum, ancak farkı anlamıyorum.

Bekleyin :

String webText = await getWebPage(uri);
await parseData(webText);

Devam Et :

Task<String> webText = new Task<String>(() => getWebPage(uri));
Task continue = webText.ContinueWith((task) =>  parseData(task.Result));
webText.Start();
continue.Wait();

Belirli durumlarda biri diğerine tercih edilir mi?


3
Kaldırılan ise Waitikinci örnekte çağrıyı daha sonra iki parçacıkları (çoğunlukla) eşdeğer olacaktır.
2013


Bilginize: getWebPageYönteminiz her iki kodda da kullanılamaz. İlk kodda Task<string>dönüş tipi varken ikinci kodda stringdönüş tipi vardır. yani temelde kodunuz derlenmez. - kesin olmak gerekirse.
Royi Namir

Yanıtlar:


101

İkinci kodda, devamın tamamlanmasını eşzamanlı olarak bekliyorsunuz. İlk versiyonda, metot, awaithenüz tamamlanmamış olan ilk ifadeye ulaşır ulaşmaz arayana geri dönecektir .

Her ikisinin de bir devamı planlaması açısından çok benzerler, ancak kontrol akışı biraz daha karmaşık hale gelir gelmez çok daha basit koda awaityol açar . Ek olarak, Servy'nin yorumlarda belirttiği gibi, bir görevin beklenmesi toplu istisnaları "açacaktır" ve bu da genellikle daha basit hata işlemeye yol açar. Ayrıca kullanmak , (siz kullanmadığınız müddetçe ) arama bağlamında devam etmeyi dolaylı olarak planlayacaktır . "El ile" yapılamayacak bir şey değil, ama bunu yapmak çok daha kolay .awaitConfigureAwaitawait

İkinizin de birlikte operasyon biraz daha büyük bir diziyi uygulamayı deneyin önermek awaitve Task.ContinueWith- bu gerçek bir göz açıcı olabilir.


2
İki parçacık arasındaki hata yönetimi de farklıdır; bu konuda awaitüzerinde çalışmak genellikle daha kolaydır ContinueWith.
2013

@Servy: Doğru, bunun etrafına bir şeyler katacak.
Jon Skeet

1
Programlama da oldukça farklı, yani hangi bağlamda parseDataçalıştırılıyor.
Stephen Cleary

Await kullanmanın, devamlılığı arama bağlamında örtük olarak planlayacağını söylediğinizde , bunun yararını ve diğer durumda ne olduğunu açıklayabilir misiniz?
Harrison

4
@Harrison: Bir WinForms uygulaması yazdığınızı hayal edin - zaman uyumsuz bir yöntem yazarsanız, varsayılan olarak yöntem içindeki kodun tamamı UI iş parçacığında çalışacaktır, çünkü devam etme orada programlanacaktır. Devam etmenin nerede çalışmasını istediğinizi belirtmezseniz, varsayılanın ne olduğunu bilmiyorum, ancak bir iş parçacığı havuzu iş parçacığında kolayca çalışmaya başlayabilir ... bu noktada UI'ye erişemezsiniz, vb. .
Jon Skeet

100

İşte async çözümlerini kullanarak farkı ve çeşitli sorunları göstermek için son zamanlarda kullandığım kod parçacıkları dizisi.

GUI tabanlı uygulamanızda çok fazla zaman alan bir olay işleyiciniz olduğunu ve bu nedenle onu eşzamansız yapmak istediğinizi varsayalım. İşte başladığınız eşzamanlı mantık:

while (true) {
    string result = LoadNextItem().Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
        break;
    }
}

LoadNextItem, sonunda incelemek istediğiniz bir sonuç üretecek bir Görev döndürür. Mevcut sonuç aradığınız sonuçsa, kullanıcı arayüzündeki bazı sayacın değerini günceller ve yöntemden dönersiniz. Aksi takdirde, LoadNextItem'den daha fazla öğe işlemeye devam edersiniz.

Eşzamansız sürüm için ilk fikir: sadece devamları kullanın! Ve şimdilik döngü kısmını görmezden gelelim. Demek istediğim, ne ters gidebilir ki?

return LoadNextItem().ContinueWith(t => {
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
});

Harika, artık engellemeyen bir yöntemimiz var! Bunun yerine çöküyor. UI kontrollerinde yapılan herhangi bir güncelleme, UI iş parçacığında yapılmalıdır, bu yüzden bunu hesaba katmanız gerekecektir. Neyse ki, devam ettirmelerin nasıl planlanacağını belirleme seçeneği var ve sadece bunun için varsayılan bir tane var:

return LoadNextItem().ContinueWith(t => {
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
},
TaskScheduler.FromCurrentSynchronizationContext());

Harika, artık çökmeyen bir yöntemimiz var! Bunun yerine sessizce başarısız oluyor. Devam etmeler, statüleri önceki görevinkine bağlı olmayan ayrı görevlerdir. Dolayısıyla, LoadNextItem hatası olsa bile, arayan yalnızca başarıyla tamamlanmış bir görevi görür. Tamam, eğer varsa istisnayı iletin:

return LoadNextItem().ContinueWith(t => {
    if (t.Exception != null) {
        throw t.Exception.InnerException;
    }
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
},
TaskScheduler.FromCurrentSynchronizationContext());

Harika, şimdi bu gerçekten çalışıyor. Tek bir öğe için. Şimdi, bu döngüye ne dersiniz? Orijinal eşzamanlı sürümün mantığına eşdeğer bir çözüm şöyle görünecektir:

Task AsyncLoop() {
    return AsyncLoopTask().ContinueWith(t =>
        Counter.Value = t.Result,
        TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
    var tcs = new TaskCompletionSource<int>();
    DoIteration(tcs);
    return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
    LoadNextItem().ContinueWith(t => {
        if (t.Exception != null) {
            tcs.TrySetException(t.Exception.InnerException);
        } else if (t.Result.Contains("target")) {
            tcs.TrySetResult(t.Result.Length);
        } else {
            DoIteration(tcs);
        }});
}

Veya yukarıdakilerin hepsi yerine, aynı şeyi yapmak için eşzamansız kullanabilirsiniz:

async Task AsyncLoop() {
    while (true) {
        string result = await LoadNextItem();
        if (result.Contains("target")) {
            Counter.Value = result.Length;
            break;
        }
    }
}

Bu şimdi çok daha güzel, değil mi?


Teşekkürler, gerçekten güzel açıklama
Elger Mensonides

Bu harika bir örnek
Royi Namir
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.