Async-await başka bir iş parçacığı oluşturmazsa, uygulamaları nasıl yanıt verir?


242

Zaman ve tekrar tekrar, bunu kullanarak söyledi bkz async- awaitherhangi bir ek konuları oluşturmaz. Bu bir anlam ifade etmiyor çünkü bir bilgisayarın aynı anda 1'den fazla şey yapıyor gibi görünebilmesinin tek yolu

  • Aslında bir seferde 1'den fazla şey yapmak (paralel olarak yürütmek, birden fazla işlemciyi kullanmak)
  • Görevleri zamanlayarak ve aralarında geçiş yaparak simüle etme (biraz A, biraz B, biraz A vb. Yapın)

Öyleyse async- awaitbunların hiçbiri yoksa, bir uygulamayı nasıl yanıt verebilir? Yalnızca 1 iş parçacığı varsa, herhangi bir yöntemi çağırmak, başka bir şey yapmadan önce yöntemin tamamlanmasını beklemek anlamına gelir ve bu yöntemin içindeki yöntemlerin devam etmeden önce sonucu beklemesi gerekir.


17
IO görevleri CPU'ya bağlı değildir ve bu nedenle bir iş parçacığı gerektirmez. Zaman uyumsuzluğunun ana noktası, IO bağlantılı görevler sırasında iş parçacıklarını engellememektir.
juharr

24
@jdweng: Hayır, hiç değil. Yeni iş parçacıkları oluştursa bile , bu yeni bir işlem oluşturmaktan çok farklıdır.
Jon Skeet

8
Geri arama tabanlı eşzamansız programlamayı anlarsanız, hiçbir iş parçacığı oluşturmadan nasıl await/ nasıl asyncçalıştığını anlarsınız .
user253751

6
Tam olarak değil yapmak bir uygulama daha duyarlı, ancak vermeyen uygulamaların yaygın nedenidir senin konuları, engelleme cesaretini gelmez.
Owen

6
@ RubberDuck: Evet, devam etmek için iş parçacığı havuzundan bir iş parçacığı kullanabilir. Ancak OP'nin burada hayal ettiği şekilde bir iş parçacığı başlatmıyor - "Bu sıradan yöntemi al, şimdi ayrı bir iş parçacığında çalıştır - orada, bu zaman uyumsuz" diyor gibi değil. Bundan çok daha incedir.
Jon Skeet

Yanıtlar:


299

Aslında, async / beklemek o kadar büyülü değil. Tüm konu oldukça geniştir ancak sorunuza hızlı ve eksiksiz bir cevap için başarabileceğimizi düşünüyorum.

Bir Windows Forms uygulamasında basit bir düğme tıklama olayını ele alalım:

public async void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("before awaiting");
    await GetSomethingAsync();
    Console.WriteLine("after awaiting");
}

Ben gidiyorum açıkça değil de ne olursa olsun hakkında konuşmak GetSomethingAsyncşimdilik geri dönüyor. Diyelim ki bu 2 saniye sonra tamamlanacak bir şey.

Geleneksel, eşzamansız olmayan bir dünyada, düğme tıklama etkinliği işleyiciniz şöyle görünür:

public void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("before waiting");
    DoSomethingThatTakes2Seconds();
    Console.WriteLine("after waiting");
}

Formdaki düğmeyi tıklattığınızda, uygulama yaklaşık 2 saniye donmuş gibi görünürken, bu yöntemin tamamlanmasını bekleriz. Olan şey, temelde bir döngü olan "mesaj pompası" nın engellenmesidir.

Bu döngü sürekli olarak pencerelere "Fareyi hareket ettirmiş gibi bir şey yaptı mı, bir şeye tıkladı mı? ve sonra "bir şey" i işler. Bu döngü, kullanıcının "button1" i (veya Windows'tan gelen eşdeğer mesaj türünü) tıkladığı ve button1_Clickyukarıdaki yöntemimizi çağırdığı bir mesaj aldı . Bu yöntem geri dönünceye kadar, bu döngü beklemede kaldı. Bu işlem 2 saniye sürer ve bu sırada hiçbir ileti işlenmez.

Pencerelerle ilgili çoğu şey mesajlar kullanılarak yapılır, yani mesaj döngüsü mesajların pompalanmasını durdurursa, bir saniye bile, kullanıcı tarafından hızlı bir şekilde fark edilir. Örneğin, not defterini veya başka bir programı kendi programınızın üzerine taşırsanız ve sonra tekrar uzaklaştırırsanız, programınıza pencerenin şimdi aniden tekrar görünür hale gelen bölgesini gösteren bir boya telaşı gönderilir. Bu mesajları işleyen mesaj döngüsü bir şey bekliyor, engelleniyorsa, boyama yapılmaz.

Yani, ilk örnekte ise, async/await yeni bir iş parçacığı oluşturmazsa, bunu nasıl yapar?

Ne olur, yönteminiz ikiye ayrılır. Bu, bu geniş konu türlerinden biridir, bu yüzden çok fazla ayrıntıya girmeyeceğim, ancak yöntemin bu iki şeye ayrıldığını söylemem yeterli:

  1. awaitÇağrısı da dahil olmak üzere tüm kodGetSomethingAsync
  2. Aşağıdaki tüm kodlar await

İllüstrasyon:

code... code... code... await X(); ... code... code... code...

Rearranged:

code... code... code... var x = X(); await X; code... code... code...
^                                  ^          ^                     ^
+---- portion 1 -------------------+          +---- portion 2 ------+

Temel olarak yöntem şöyle çalışır:

  1. Kadar her şeyi yürütür await
  2. İşini yapan GetSomethingAsyncyöntemi çağırır ve gelecekte 2 saniye tamamlayacak bir şey döndürür

    Şimdiye kadar, mesaj döngüsünden çağrılan ana iş parçacığında gerçekleşen, button1_Click'e yapılan orijinal çağrı içerisindeyiz. Kodu açan awaitçok zaman alırsa, kullanıcı arayüzü yine de donar. Örneğimizde çok fazla değil

  3. Ne await kelime, araya bazı akıllı derleyici büyü ile, yaptığı temelde gibi bir şey "Tamam, sen, ben sadece burada düğmesini tıklatın olay işleyicisi dönmek için gidiyorum biliyorum. Ne zaman bir şey olduğu gibi (ki biz' dir beklemek) tamamlamak için dolaşmak, bana bildirin çünkü hala yürütmek için bazı kod kaldı ".

    Aslında SynchronizationContext sınıfının yapıldığını bilmesine izin verir, şu anda oynatılmakta olan gerçek senkronizasyon içeriğine bağlı olarak yürütme için sıraya girer. Windows Forms programında kullanılan bağlam sınıfı, ileti döngüsünün pompaladığı kuyruğu kullanarak bu kuyruğu kuyruğa alır.

  4. Böylece, pencereyi hareket ettirme, yeniden boyutlandırma veya diğer düğmeleri tıklatma gibi iletileri pompalamaya devam etmekte serbest olan mesaj döngüsüne geri döner.

    Kullanıcı için, kullanıcı arayüzü şimdi tekrar yanıt veriyor, diğer düğme tıklamalarını işliyor, yeniden boyutlandırıyor ve en önemlisi yeniden çiziyor , bu yüzden donmuş gibi görünmüyor.

  5. 2 saniye sonra, beklediğimiz şey tamamlanıyor ve şimdi olan şey (iyi, senkronizasyon bağlamı) mesaj döngüsünün baktığı kuyruğa bir mesaj yerleştiriyor ve "Hey, çalıştırmanız gerekir "ve bu kod bekledikten sonra tüm koddur .
  6. İleti döngüsü bu iletiye ulaştığında, temelde o yöntemi bıraktığı yerden hemen sonra yeniden girer awaitve yöntemin geri kalanını yürütmeye devam eder. Bu kodun mesaj döngüsünden tekrar çağrıldığını unutmayın, bu nedenle bu kod async/awaitdüzgün bir şekilde kullanmadan uzun bir şey yaparsa , mesaj döngüsünü tekrar engeller

Birçok hareketli parçalar daha fazla bilgi için bazı bağlantılar işte burada başlık altında vardır, ben "bunu gerektiğinde" diyecektim, ama bu konu olduğunu oldukça geniş ve bilmek oldukça önemlidir olanlar hareketli parçaların bazıları . Her zaman asenkron / beklemenin hala sızdıran bir kavram olduğunu anlayacaksınız. Temeldeki sınırlamalardan ve sorunlardan bazıları hala çevre koduna sızıyor ve eğer yapmazlarsa, görünüşte iyi bir neden olmadığı için rastgele kırılan bir uygulamada hata ayıklamak zorunda kalırsınız.


Tamam, peki ya GetSomethingAsync2 saniye içinde tamamlanacak bir iplik döndürürse? Evet, tabii ki oyunda yeni bir iş parçacığı var. Bu iplik, ancak, değil , çünkü bu yöntemin programcı asenkron kod uygulamak için bir iş parçacığı seçtik çünkü öyle, bu yöntemin zaman uyumsuz-lik. Hemen hemen tüm asenkron I / O yok bir iş parçacığı kullanmak, bunlar farklı şeyler kullanırlar. async/await kendi başlarına yeni dişler açmazlar, fakat “beklediğimiz şeyler” dişler kullanılarak uygulanabilir.

.NET'te mutlaka kendi başlarına bir iş parçacığı döndürmek değil ama yine de senkronize olmayan birçok şey vardır:

  • Web istekleri (ve zaman alan ağla ilgili diğer birçok şey)
  • Zaman uyumsuz dosya okuma ve yazma
  • Söz konusu sınıf / arabirim seçti yöntemler ise ve daha birçok, iyi bir işarettir SomethingSomethingAsyncveya BeginSomethingve EndSomethingbir var IAsyncResultdahil.

Genellikle bu şeyler kaputun altında bir iplik kullanmaz.


Tamam, yani o "geniş konu" ların bazılarını mı istiyorsunuz?

Peki, Roslyn'i deneyin düğmemizi soralım :

Roslyn'i deneyin

Burada tam olarak üretilen sınıfta bağlantı yapmayacağım ama oldukça kanlı şeyler.


11
Temel olarak OP'nin " Görevleri zamanlayarak ve aralarında geçiş yaparak paralel yürütmeyi simüle etme " olarak tanımladığı şey, değil mi?
Bergi

4
@Bergi Pek değil. Yürütme gerçekten paraleldir - eşzamansız G / Ç görevi devam ediyor ve devam etmek için hiçbir iş parçacığı gerektirmiyor (bu, Windows gelmeden çok önce kullanılan bir şeydir - MS DOS, eşzamansız G / Ç'yi kullansa da kullanmıştır. çoklu iş parçacığı var!). Tabii ki, await olabilir iyi olarak tarif şekilde kullanılabilir, ancak genellikle değildir edilebilir. Yalnızca geri çağrılar zamanlanır (iş parçacığı havuzunda) - geri arama ve istek arasında, iş parçacığına gerek yoktur.
Luaan

3
Bu nedenle, async / beklemekle ilgili soru özellikle kendi iş parçacıklarını oluşturmadığı için, bu yöntemin ne yaptığı hakkında çok fazla konuşmaktan kaçınmak istedim. Açıkçası, dişlerin tamamlanmasını beklemek için kullanılabilirler .
Lasse V. Karlsen

6
@ LasseV.Karlsen - Harika cevabınızı yutuyorum, ama hala bir ayrıntıya asıldım. Olay işleyicisinin, ileti pompasının pompalamaya devam etmesini sağlayan 4. adımda olduğu gibi var olduğunu, ancak "iki saniye süren" ayrı bir iş parçacığında değilse ne zaman ve nerede yürütülmeye devam ettiğini anlıyorum. UI iş parçacığında yürütmek için olsaydı , aynı ileti üzerinde bir süre yürütmek zorunda çünkü yürütme sırasında ileti pompasını yine de engelleyecekti .. [devam] ...
rory.ap

3
Mesaj pompasıyla açıklamanızı beğendim. Konsol uygulamasında veya web sunucusunda olduğu gibi bir mesaj pompası olmadığında açıklamanız nasıl değişir? Bir yöntemin tekrar girilmesi nasıl sağlanır?
Puchacz

95

Benim blog yazısı tam olarak açıklamak hiçbir Konu Var mı .

Özetle, modern I / O sistemleri DMA'yı (Doğrudan Bellek Erişimi) yoğun şekilde kullanır. Ağ kartlarında, ekran kartlarında, HDD denetleyicilerinde, seri / paralel bağlantı noktalarında vb. Özel, özel işlemciler vardır. Bu işlemciler bellek veriyoluna doğrudan erişebilir ve CPU'dan tamamen bağımsız olarak okuma / yazma işlemlerini gerçekleştirebilir. CPU, sadece verileri içeren bellekteki konumu cihaza bildirmelidir ve daha sonra cihaz, okuma / yazma işleminin tamamlandığını CPU'ya bildiren bir kesinti yapana kadar kendi işini yapabilir.

İşlem uçuştan sonra, CPU'nun yapacağı bir iş yoktur ve bu nedenle iplik yoktur.


Sadece açıklığa kavuşturmak için .. Async-await kullanırken olanların yüksek seviyesini anlıyorum. Hiçbir iş parçacığı oluşturma ile ilgili - sadece sizin gibi kendi işlemcileri kendi istekleri işlemek var dedi aygıtlara G / Ç istekleri iş parçacığı yoktur? TÜM G / Ç isteklerinin bu tür bağımsız işlemciler üzerinde gerçekleştirildiğini varsayabilir miyiz?
Yonatan Nir

@YonatanNir: Bu sadece ayrı işlemcilerle ilgili değil; her türlü olaya dayalı yanıt doğal olarak eşzamansızdır. Task.RunCPU'ya bağlı eylemler için en uygun olanıdır , ancak birtakım başka kullanımları da vardır.
Stephen Cleary

1
Makalenizi okumayı bitirdim ve hala işletim sisteminin daha düşük düzeydeki uygulamasına aşina olmadığım için anlamadığım temel bir şey var. Ne yazdığınızı yazdığınız yere aldım: "Yazma işlemi şimdi" uçuşta ". Kaç iş parçacığı işliyor? Yok." . Peki hiçbir iş parçacığı yoksa, işlem bir iş parçacığı üzerinde değilse nasıl yapılır?
Yonatan Nir

6
Bu binlerce açıklama eksik parçası !!! Aslında I / O operasyonları ile arka planda işi yapan biri var. Bir iş parçacığı değil, işini yapan başka bir özel donanım bileşeni!
the_dark_destructor

2
@PrabuWeerasinghe: Derleyici, durumu ve yerel değişkenleri tutan bir yapı oluşturur. Bir beklemenin vermesi gerekiyorsa (yani, arayanına geri dönün), bu yapı kutlanır ve yığın üzerinde yaşar.
Stephen Cleary

87

bir bilgisayarın bir seferde 1'den fazla şey yapıyor gibi görünebilmesinin tek yolu (1) Aslında bir seferde 1'den fazla şey yapmak, (2) görevleri zamanlamak ve aralarında geçiş yapmak suretiyle simüle etmek. Yani eğer eşzamansız-beklemiyorsa

Bunların hiçbirini beklemediği değil . Unutmayın, amacı senkronize kodu sihirli bir şekilde eşzamansız yapmakawait değildir . Bu etkinleştirmek için var asenkron koduna çağrılırken biz senkron kod yazmak için kullandığınız teknikler kullanılarak . Bekleme, yüksek gecikme işlemleri kullanan kodun düşük gecikme işlemleri kullanan kod gibi görünmesi ile ilgilidir . Bu yüksek gecikme işlemleri iş parçacığı üzerinde olabilir, özel amaçlı donanımda olabilir, çalışmalarını küçük parçalara ayırıyor ve daha sonra UI iş parçacığı tarafından işlenmek üzere mesaj kuyruğuna koyuyor olabilirler. Bir şey asenkroniye elde etmek, ancak onlarbunu yapanlardır. Bekle, bu eşzamansızlıktan yararlanmanıza izin verir.

Ayrıca, üçüncü bir seçeneği kaçırdığını düşünüyorum. Biz yaşlı insanlar - bugün rap müziği olan çocuklar çimimden inmeli, vb - 1990'ların başında Windows dünyasını hatırlıyoruz. Çoklu CPU makineleri ve iş parçacığı zamanlayıcıları yoktu. Sen zorunda kaldı aynı anda iki, Windows uygulamalarını çalıştırmak istediğini verim . Çoklu görev kooperatifti . İşletim sistemi, işlemin gerçekleştiğini söyler ve eğer kötü davranırsa, diğer tüm işlemlere hizmetten açlıktan ölür. Verim elde edilene kadar çalışır ve bir şekilde OS elleri tekrar kontrol edişinde kaldığı yerden nasıl kalkacağını bilmelidir.. Tek iş parçacıklı eşzamansız kod çok benzer, "verim" yerine "bekliyor". Beklemek, "Burada kaldığım yeri hatırlayacağım ve bir başkasının kaçmasına izin vereceğim; beklediğim görev tamamlandığında beni geri ara ve bıraktığım yerden devam edeceğim." Bunun, Windows 3 günlerinde olduğu gibi, uygulamaları nasıl daha duyarlı hale getirdiğini görebilirsiniz.

herhangi bir yöntemin çağrılması, yöntemin tamamlanmasını beklemek anlamına gelir

Eksik olan anahtar var. Bir yöntem, işi tamamlanmadan geri dönebilir . Tam da buradaki eşzamansızlığın özü budur. Bir yöntem geri döner, "bu çalışma devam ediyor; tamamlandığında ne yapacağımı söyle" anlamına gelen bir görev döndürür. Yöntemin işi, geri dönmesine rağmen yapılmaz .

Operatörü beklemeden önce, tamamlandıktan sonra yapacak işlerimiz olduğu , ancak dönüş ve tamamlanma zaman uyumsuzluğuyla başa çıkmak için İsviçre peynirinden geçirilen spagetti gibi görünen bir kod yazmanız gerekiyordu . Bekliyoruz o kod yazmasına olanak tanır bakışlar iade ve tamamlanması gibi senkronize edilir onları olmadan, aslında senkronize ediliyor.


Diğer modern üst düzey diller de benzer şekilde işbirlikçi davranışları desteklemektedir (yani işlev bazı şeyler yapar, verim [muhtemelen arayana bir değer / nesne gönderir], kontrol geri verildiğinde kaldığı yerden devam eder [muhtemelen ek girdi sağlandığında] ). Jeneratörler bir kere Python'da oldukça büyük.
JAB

2
@JAB: Kesinlikle. Jeneratörler C # 'da "yineleyici blokları" olarak adlandırılır ve yieldanahtar kelimeyi kullanır . asyncC # 'daki hem yöntemler hem de yineleyiciler , geçerli işleminin daha sonra yeniden başlatılması için nasıl askıya alınacağını bilen bir işlev için genel terim olan bir coroutine biçimidir . Günümüzde birçok dilde koroutin veya koroutin benzeri kontrol akışı vardır.
Eric Lippert

1
Verilecek benzetme iyi bir yöntemdir - tek bir süreç içinde işbirliğine dayalı çoklu görevdir . (ve böylece sistem çapında ortak çoklu görevlerin sistem kararlılığı sorunlarından kaçınılması)
user253751

3
Bence IO için kullanılan "cpu kesintileri" kavramı, bir çok modem "programcı" hakkında bir şey bilmiyor, bu nedenle bir iş parçacığının her bit için beklemesi gerektiğini düşünüyorlar.
Ian Ringrose

WebClient'in @EricLippert Async yöntemi aslında ek iş parçacığı yaratıyor, buraya bakın stackoverflow.com/questions/48366871/…
KevinBui

28

Birinin bu soruyu sorduğuna gerçekten sevindim, çünkü uzun zamandır konuların eşzamanlılık için gerekli olduğuna inandım. Olay döngülerini ilk gördüğümde , onların yalan olduğunu düşündüm. Kendi kendime "tek bir iş parçacığında çalışırsa bu kod eşzamanlı olabilir hiçbir yolu" diye düşündüm. Bunun, eşzamanlılık ve paralellik arasındaki farkı anlama mücadelesinden geçtikten sonra olduğunu unutmayın .

Benim kendi araştırma sonra sonunda kayıp parçasını bulduk: select(). Özellikle, farklı isimler altında çeşitli çekirdekler tarafından uygulanan IO çoklama,: select(), poll(), epoll(), kqueue(). Bunlar , uygulama ayrıntıları farklı olsa da, izlemek için bir dizi dosya tanımlayıcısını iletmenize izin veren sistem çağrılarıdır . Ardından, izlenen dosya tanımlayıcılarından biri değişinceye kadar engellenen başka bir çağrı yapabilirsiniz.

Böylece, bir dizi IO olayı (ana olay döngüsü) üzerinde beklenebilir, tamamlanan ilk olayı işleyebilir ve daha sonra olay döngüsüne geri kontrol verebilir. Durulayın ve tekrarlayın.

Bu nasıl çalışıyor? Kısa cevap, çekirdek ve donanım düzeyinde sihir olmasıdır. Bilgisayarda CPU dışında birçok bileşen vardır ve bu bileşenler paralel olarak çalışabilir. Çekirdek bu cihazları kontrol edebilir ve belirli sinyalleri almak için onlarla doğrudan iletişim kurabilir.

Bu GÇ çoğullama sistemi çağrıları, node.js veya Tornado gibi tek iş parçacıklı olay döngülerinin temel yapı taşıdır. Bir awaitişlev yaptığınızda, belirli bir olayı izlersiniz (bu işlevin tamamlanması) ve ardından denetimi ana olay döngüsüne geri döndürürsünüz. İzlediğiniz olay tamamlandığında, işlev (sonunda) kaldığı yerden devam eder. Bunun gibi hesaplamayı askıya almanıza ve devam ettirmenize izin veren işlevlere coroutines denir .


25

awaitve Konular değil Görevler'iasync kullanın .

Çerçeve, Görev nesneleri biçiminde bazı işleri yürütmeye hazır bir iş parçacığı havuzuna sahiptir ; havuza Görev göndermek , görev eylem yöntemini çağırmak için ücretsiz, zaten var olan 1 iş parçacığını seçmek anlamına gelir .
Bir Oluşturma Görevi uzak yolu daha hızlı yeni bir iş parçacığı oluşturmak yerine, yeni bir nesne oluşturma önemi olduğunu.

Verilen bir görev bir takmak mümkündür Sürmesi bir yeni, kendisine görev iplik uçları bir kez yürütülecek nesne.

Yana async/awaitkullanılması Görev s onlar yok yeni oluşturmak konu.


Kesme programlama tekniği her modern işletim sisteminde yaygın olarak kullanılsa da, burada alakalı olduklarını düşünmüyorum. Tek bir CPU kullanarak paralel olarak (aslında araya eklenmiş) yürütülen
iki CPU bağlı göreve sahip olabilirsiniz aysnc/await.
Bu, işletim sisteminin IORP kuyruğunu desteklemesi gerçeğiyle açıklanamaz .


Derleyici dönüştürülen asyncyöntemleri DFA'ya en son kontrol ettiğimde , çalışma adımlara ayrılır, her biri bir awaittalimatla sona erer . Onun başlar Görev ve ona bir sonraki adımı yürütmek için bir devamı ekleyin.
await

Kavram örneği olarak, burada bir sahte kod örneği verilmiştir.
Açıklık uğruna işler basitleştiriliyor ve tüm detayları tam olarak hatırlamıyorum.

method:
   instr1                  
   instr2
   await task1
   instr3
   instr4
   await task2
   instr5
   return value

Böyle bir şeye dönüşüyor

int state = 0;

Task nextStep()
{
  switch (state)
  {
     case 0:
        instr1;
        instr2;
        state = 1;

        task1.addContinuation(nextStep());
        task1.start();

        return task1;

     case 1:
        instr3;
        instr4;
        state = 2;

        task2.addContinuation(nextStep());
        task2.start();

        return task2;

     case 2:
        instr5;
        state = 0;

        task3 = new Task();
        task3.setResult(value);
        task3.setCompleted();

        return task3;
   }
}

method:
   nextStep();

1 Aslında bir havuzun görev oluşturma politikası olabilir.


16

Eric Lippert veya Lasse V. Karlsen ve diğerleri ile rekabet etmeyeceğim, sadece bu sorunun başka bir yönüne dikkat çekmek istiyorum, açıkça belirtilmediğini düşünüyorum.

awaitKendi başına kullanmak uygulamanızı sihirli bir şekilde duyarlı yapmaz. UI iş parçacığı bloklarından beklediğiniz yöntemde ne yaparsanız yapın , yine de UI'nizi, beklenmedik sürümle aynı şekilde engeller .

Yeni bir iş parçacığı oluşturmak veya tamamlama bağlantı noktası (geçerli iş parçacığında yürütmeyi döndürecek ve tamamlama bağlantı noktası her sinyal verildiğinde devam etmek için başka bir şey çağıracak) gibi bir şey kullanmak için beklediğiniz yöntemi özellikle yazmanız gerekir. Ancak bu kısım diğer cevaplarda iyi açıklanmıştır.


3
İlk etapta bir rekabet değil; bu bir işbirliği!
Eric Lippert

16

Tüm bunları nasıl gördüğüm, süper teknik olarak doğru olmayabilir ama en azından bana yardımcı olur :).

Bir makinede temel olarak iki tür işlem (hesaplama) vardır:

  • CPU'da gerçekleşen işleme
  • diğer işlemcilerde (GPU, ağ kartı vb.) gerçekleşen işleme, onlara IO diyelim.

Bu nedenle, bir kaynak kod parçası yazdığımızda, derlemeden sonra, kullandığımız nesneye bağlı olarak (ve bu çok önemlidir), işlem CPU'ya bağlı veya IO'ya bağlı olacak ve aslında, her ikisi de.

Bazı örnekler:

  • FileStreamnesnenin Yazma yöntemini kullanırsam (bu bir Akıştır), işleme% 1 CPU bağlı ve% 99 IO bağlı olarak söylenir.
  • NetworkStreamnesnenin Yazma yöntemini kullanırsam (bu bir Akıştır), işleme% 1 CPU bağlı ve% 99 IO bağlı olarak söylenir.
  • Memorystreamnesnenin (bu bir Akış) Yazma yöntemini kullanırsam , işlem% 100 CPU bağlı olacaktır.

Gördüğünüz gibi, bir nesneye yönelik programcı bakış açısından, her zaman bir Streamnesneye erişmeme rağmen , altında ne olacağı büyük ölçüde nesnenin nihai türüne bağlı olabilir.

Şimdi, bir şeyleri optimize etmek için, mümkünse ve / veya gerekliyse, kodu paralel olarak çalıştırabilmek yararlı olabilir (notu eşzamansız kelimeyi kullanmadığımı unutmayın).

Bazı örnekler:

  • Masaüstü uygulamasında, bir belge yazdırmak istiyorum, ancak beklemek istemiyorum.
  • Web sunucum aynı anda birçok istemciyi sunucuya gönderiyor, her biri sayfalarını paralel olarak alıyor (serileştirilmemiş).

Zaman uyumsuz / beklemeden önce, buna iki çözümümüz vardı:

  • Konular . Thread ve ThreadPool sınıflarıyla kullanımı nispeten kolaydı. İş parçacıkları yalnızca CPU'ya bağlıdır .
  • "Eski" Begin / End / AsyncCallback zaman uyumsuz programlama modeli. Bu sadece bir model, CPU veya IO'ya bağlı olup olmayacağınızı söylemiyor. Socket veya FileStream sınıflarına bakarsanız, IO bağlı, bu harika, ancak nadiren kullanıyoruz.

Async / await, Görev kavramına dayanan yalnızca ortak bir programlama modelidir . CPU bağlantılı görevler için iş parçacıklarından veya iş parçacığı havuzlarından biraz daha kolay ve eski Başlangıç ​​/ Bitiş modelinden çok daha kolay. Bununla birlikte, altını örter, her ikisi de süper sofistike bir özellik dolu sarıcı "sadece".

Yani, gerçek kazanç çoğunlukla IO Bound görevlerinde , CPU kullanmayan görevde, ancak asenkron / bekliyor hala sadece bir programlama modelidir, sonunda işlemenin nasıl / nerede olacağını belirlemenize yardımcı olmaz.

Bunun nedeni, bir sınıfın, bir Görev nesnesini döndüren ve bir CPU nesnesine bağlı olduğunu varsayabileceğiniz bir yöntemi "DoSomethingAsync" olması olmadığı anlamına gelmez (yani, bir iptal belirteci parametresi yoksa oldukça işe yaramaz olabilir) veya IO Bound (bu muhtemelen bir zorunluluktur ) veya her ikisinin bir kombinasyonu (model oldukça viral olduğu için, bağlanma ve potansiyel faydalar sonunda süper karışık ve çok açık olmayabilir).

Bu yüzden, örneklerime geri dönersek, MemoryStream üzerinde async / await kullanarak Yazma işlemlerini yapmak CPU'yu bağlı tutacaktır (muhtemelen bundan faydalanmayacağım), ancak kesinlikle dosya ve ağ akışlarından faydalanacağım.


1
Bu CPU bağlı iş için theadpool kullanarak oldukça iyi bir cevap zayıf IO operasyonlarını boşaltmak için TP iş parçacığı kullanılması gerektiği açısından zayıftır. CPU bağlı iş imo elbette uyarılar ile engelleme olmalı ve hiçbir şey birden fazla iş parçacığının kullanımını engellemez.
davidcarr

3

Diğer cevapları özetleme:

Async / await öncelikle IO bağlı görevler için oluşturulur, çünkü bunları kullanarak arama iş parçacığını engellemekten kaçınabilirsiniz. Ana kullanımları, IO bağlı bir işlemde iş parçacığının engellenmesinin istenmediği UI iş parçacıklarıdır.

Async kendi iş parçacığını oluşturmaz. Çağıran yöntemin iş parçacığı, async yöntemini beklenen bir sonuç bulana kadar yürütmek için kullanılır. Aynı iş parçacığı daha sonra çağrı yönteminin geri kalanını zaman uyumsuz yöntem çağrısının ötesinde yürütmeye devam eder. Çağrılan zaman uyumsuz yöntem içinde, beklemeden döndükten sonra, devam iş parçacığı havuzundan bir iş parçacığı üzerinde yürütülebilir - ayrı bir iş parçacığı resme tek yer.


İyi bir özet, ama tam resmi vermek için 2 soruya cevap vermesi gerektiğini düşünüyorum: 1. Beklenen kod ne iş parçacığı yürütülür? 2. Bahsedilen iş parçacığı havuzunu kim geliştirir veya yapılandırır?
stojke

1. Bu durumda, çoğunlukla beklenen kod, CPU iş parçacıklarını kullanmayacak bir GÇ bağlı işlemdir. CPU bağlantılı çalışma için beklemek isteniyorsa, ayrı bir Görev ortaya çıkabilir. 2. İş parçacığı havuzundaki iş parçacığı, TPL çerçevesinin bir parçası olan Görev zamanlayıcı tarafından yönetilir.
vaibhav kumar

2

Aşağıdan açıklamaya çalışıyorum. Belki birileri yardımcı olabilir. Ben oradaydım, bunu yaptım, yeniden icat ettim, Pascal'da DOS'ta basit oyunlar yaptıklarında

Yani ... Her olay güdümlü uygulamada, içinde böyle bir olay döngüsü olan bir olay döngüsü vardır:

while (getMessage(out message)) // pseudo-code
{
   dispatchMessage(message); // pseudo-code
}

Çerçeveler genellikle bu ayrıntıyı sizden gizler, ancak oradadır. GetMessage işlevi, olay kuyruğundan sonraki olayı okur veya bir olay gerçekleşene kadar bekler: fare hareketi, tuşlar, tuşlar, tıklama vb. Ve sonra dispatchMessage olayı uygun olay işleyicisine gönderir. Sonra bir sonraki olayı bekler ve döngülerden çıkıp uygulamayı tamamlayan bir quit olayı gelene kadar devam eder.

Olay işleyicileri hızlı çalışmalıdır, böylece olay döngüsü daha fazla olay için yoklanabilir ve kullanıcı arayüzü duyarlı kalır. Bir düğme tıklaması böyle pahalı bir işlemi tetiklerse ne olur?

void expensiveOperation()
{
    for (int i = 0; i < 1000; i++)
    {
        Thread.Sleep(10);
    }
}

Kontrol işlev içinde kaldıkça kullanıcı arayüzü 10 saniyelik işlem bitene kadar yanıt vermiyor. Bu sorunu çözmek için görevi hızlı bir şekilde yürütülebilecek küçük parçalara ayırmanız gerekir. Bu, her şeyi tek bir olayda ele alamayacağınız anlamına gelir. Çalışmanın küçük bir bölümünü yapmalı, sonra başka bir etkinlik yayınlamalısınız devam etmesini istemek için olay kuyruğuna göndermelisiniz.

Yani bunu şu şekilde değiştirebilirsiniz:

void expensiveOperation()
{
    doIteration(0);
}

void doIteration(int i)
{
    if (i >= 1000) return;
    Thread.Sleep(10); // Do a piece of work.
    postFunctionCallMessage(() => {doIteration(i + 1);}); // Pseudo code. 
}

Bu durumda yalnızca ilk yineleme çalışır, ardından bir sonraki yinelemeyi çalıştırmak için olay kuyruğuna bir ileti gönderir ve geri döner. Bizim örneğimizpostFunctionCallMessage pseudo fonksiyonumuz kuyruğa bir "this function call" olayı koyar, böylece olay dağıtıcısı ona ulaştığında çağırır. Bu, uzun süreli bir çalışmanın parçalarını sürekli olarak çalıştırırken diğer tüm GUI olaylarının işlenmesine izin verir.

Bu uzun süren görev çalıştığı sürece, devam etme olayı her zaman olay kuyruğundadır. Yani temel olarak kendi görev zamanlayıcınızı icat ettiniz. Sıradaki devam olaylarının çalışmakta olan "işlemler" olduğu yerler. Aslında bu, işletim sistemlerinin yaptığı şeydir, ancak devam olaylarının gönderilmesi ve zamanlayıcı döngüsüne geri dönülmesi, işletim sisteminin bağlam anahtarlama kodunu kaydettiği CPU'nun zamanlayıcı kesintisi yoluyla yapılır, bu yüzden ilgilenmeniz gerekmez. Ama burada kendi zamanlayıcınızı yazıyorsunuz, bu yüzden bunu önemsemeniz gerekiyor - şimdiye kadar.

Böylece, uzun süren görevleri GUI'ye paralel olarak tek bir iş parçacığında küçük parçalara ayırarak ve devam olayları göndererek çalıştırabiliriz. TaskSınıfın genel fikri budur . Bir iş parçasını temsil eder ve .ContinueWithüzerinde arama yaptığınızda, geçerli parça bittiğinde (ve dönüş değeri devamına aktarıldığında) bir sonraki parça olarak adlandırılacak işlevi tanımlarsınız. TaskSınıf I başında gösterdi istemek benzer iş parçalarını yapmak için bekleyen her iş parçacığı bir olay döngü olduğu bir iş parçacığı havuzu kullanır. Bu şekilde milyonlarca görevi paralel olarak çalıştırabilirsiniz, ancak bunları çalıştırmak için yalnızca birkaç iş parçacığına sahip olabilirsiniz. Ancak, tek bir iş parçacığıyla da işe yarayacaktır - görevleriniz düzgün bir şekilde küçük parçalara ayrıldığı sürece, her biri parellelde çalışıyor gibi görünür.

Ancak tüm bu zincirleme işini el ile küçük parçalara ayırmak zahmetli bir iştir ve mantığın düzenini tamamen bozar, çünkü tüm arka plan görev kodu temelde bir .ContinueWithkarışıklıktır. Böylece derleyici size yardımcı olur. Tüm bu zincirleme ve devamı sizin için arka planda yapar. awaitDerleyiciye "burada dur, işlevin geri kalanını devam etme görevi olarak ekle" dediğini söylediğinde . Derleyici gerisini halleder, bu yüzden yapmanız gerekmez.


0

Aslında, async awaitzincirler CLR derleyicisi tarafından üretilen durum makinesidir.

async await ancak TPL'nin görevleri yürütmek için iş parçacığı havuzu kullandığı iş parçacıkları kullanır.

Uygulamanın engellenmemesinin nedeni, durum makinesinin hangi rutinin çalıştırılacağı, tekrarlanacağı, kontrol edileceği ve tekrar karar vereceğine karar verebilmesidir.

Daha fazla okuma:

Eşzamansız ve ne bekliyor?

Async Bekliyor ve Üretilen StateMachine

Asenkron C # ve F # (III.): Nasıl çalışır? - Tomas Petricek

Düzenle :

Tamam. Ayrıntılarım yanlış görünüyor. Ancak, devlet makinelerinin async awaits için önemli varlıklar olduğunu belirtmek zorundayım . Eşzamansız G / Ç'yi alsanız bile, işlemin tamamlanıp tamamlanmadığını kontrol etmek için hala bir yardımcıya ihtiyacınız vardır, bu nedenle hala bir durum makinesine ihtiyacımız vardır ve hangi rutinin birlikte eşzamanlı olarak yürütülemeyeceğini belirleriz.


0

Bu doğrudan soruyu cevaplamıyor, ancak ilginç bir ek bilgi olduğunu düşünüyorum:

Async ve await tek başına yeni evreler oluşturmaz. ANCAK, zaman uyumsuz beklemeyi kullandığınız yere bağlı olarak, beklemeden ÖNCE eşzamanlı bölüm, beklemeden SONRA eşzamanlı bölümden farklı bir iş parçacığında çalışabilir (örneğin ASP.NET ve ASP.NET çekirdeği farklı davranır).

UI-Thread tabanlı uygulamalarda (WinForms, WPF) önce ve sonra aynı iş parçacığında olacaksınız. Ancak bir iş parçacığı havuzu iş parçacığında async kullandığınızda, bekleme öncesi ve sonrası iş parçacığı aynı olmayabilir.

Bu konuda harika bir video

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.