internet pencereler Android
Genişletmek

Asenkron programlama. Eşzamansız Delegeler Eşzamansız Programlama Kod Örnekleri c Sıralama

Programlamada eşzamansızlık

İvan Borisov

Geleneksel programlama, eşzamanlı programlamayı kullanır - diskten okuma gibi bir sistem işlemi tamamlanana kadar yürütme iş parçacığını tamamen engelleyen eşzamanlı sistem çağrılarıyla talimatların sıralı yürütülmesi. Örnek olarak echo sunucusu aşağıda yazılmıştır:

While (true) ( ​​std::string data; auto soket = Socket(localhost, port); Socket.wait_connection(); while (!socket.end_of_connection()) ( data = Socket.read(); // Engelleme Socket.write(veri); // Kilitle ) )

Read() ve write() yöntemleri çağrıldığında, ağ G/Ç'si beklenirken geçerli yürütme iş parçacığı kesintiye uğrayacaktır. Üstelik çoğu zaman program bekleyecektir. Yüksek yüklü sistemlerde, en sık görülen şey budur - neredeyse her zaman program bir şeyi bekler: bir disk, bir DBMS, bir ağ, bir kullanıcı arayüzü, genel olarak programın kendisinden bağımsız bazı harici olaylar. Hafif yüklü sistemlerde bu durum, her engelleme eylemi için yeni bir iş parçacığı oluşturularak çözülebilir. Bir iş parçacığı uyurken diğeri çalışıyor.

Peki çok sayıda kullanıcı olduğunda ne yapmalı? Her biri için en az bir iş parçacığı oluşturursanız, iş parçacığının yürütme içeriğinin sürekli değişmesi nedeniyle böyle bir sunucunun performansı keskin bir şekilde düşecektir. Ayrıca her iş parçacığının, minimum boyutu 4 KB olan yığın belleği de dahil olmak üzere kendi yürütme bağlamı vardır. Asenkron programlama bu sorunu çözebilir.

Eşzamansızlık

Programlamada eşzamansızlık, program iş parçacığının işleme devam etmesine izin veren, engellemeyen bir sistem çağrısı modunda bir işlemin yürütülmesidir. Aşağıda öğreneceğiniz asenkron programlamayı uygulamanın birkaç yolu vardır.

Geri aramalar

Eşzamansız bir program yazmak için, geri çağırma işlevlerini (İngilizce geri arama - geri aramadan) - görev tamamlandıktan sonra bazı olay işleyicileri tarafından eşzamansız olarak çağrılacak işlevler kullanabilirsiniz. Geri çağırma işlevlerini kullanan bir sunucunun yeniden yazılmış örneği:

While (true) ( ​​auto Socket = Socket(localhost, port); Socket.wait_connection(); // Hala engellendi * soketten her yeni veri alındıktan sonra, * ve ana iş parçacığı yeni bir soket oluşturmaya gidecek ve * yeni bir bağlantı bekleyecektir. */ ( soket.async_write(data, (auto &socket) ( if (socket.end_of_connection) ()) soket.close(); ) ); )); )

wait_connection()'da hala bir şeyler bekliyoruz, ancak şimdi wait_connection() işlevinin içinde işletim sistemi zamanlayıcısı gibi bir şey uygulanabilir, ancak geri çağırma işlevleriyle (yeni bir bağlantı beklerken neden eskileri işlemeyelim?) ? Örneğin, kuyruk yoluyla). Yuvada yeni veriler belirirse (async_read()'da lambda) veya async_write()'da veri yazılmışsa (lambda) geri çağırma işlevi çağrılır.

Sonuç olarak, çok daha az bekleyecek olan tek bir iş parçacığında birkaç bağlantının eşzamansız çalışmasını elde ettik. Bu eşzamansızlık, CPU zamanının kullanımından tam kazanç elde etmek için paralelleştirilebilir.

Bu yaklaşımla ilgili çeşitli sorunlar var. İlkine şaka yollu geri arama cehennemi deniyor. Bu konunun ne kadar okunmaz ve çirkin olduğunu anlamak için Google'daki resimlere bakmak yeterli. Örneğimizde yalnızca iki iç içe geri çağırma işlevi vardır, ancak çok daha fazlası olabilir.

İkinci sorun, kodun artık eşzamanlı görünmemesidir: wait_connection() öğesinden lambdalara "atlamalar" vardır, örneğin async_write() işlevine iletilen lambda gibi, bu da kodun sırasını bozar ve hangi sırada olduğunu tahmin etmeyi imkansız hale getirir. lambdalar denir. Bu, kodun okunmasını ve anlaşılmasını zorlaştırır.

Eşzamansız/Beklemede

Senkron gibi görünmesi için asenkron kod yapmaya çalışalım. Daha iyi anlaşılması için görevi biraz değiştirelim: şimdi ağ üzerinden iletilen bir anahtarı kullanarak DBMS'den verileri ve bir dosyayı okumamız ve sonucu ağ üzerinden geri göndermemiz gerekiyor.

Genel eşzamansız void work() ( var db_conn = Db_connection(localhost); var Socket = Socket(localhost, port); Socket.wait_connection(); var data = Socket.async_read(); var db_data = db_conn.async_get(veri bekleniyor) ; var file_data = Dosya(veri bekleniyor).async_read(); wait soket.async_write($”(db_data bekleniyor) (file_data bekleniyor)”); Socket.close(); )

Programı satır satır inceleyelim:

  • Bir işlev başlığındaki async anahtar sözcüğü, derleyiciye işlevin eşzamansız olduğunu ve farklı şekilde derlenmesi gerektiğini bildirir. Bunu tam olarak nasıl yapacağı aşağıda yazılıdır.
  • İşlevin ilk üç satırı: bağlantı oluşturma ve bekleme.
  • Bir sonraki satır, yürütmenin ana iş parçacığını kesintiye uğratmadan eşzamansız okuma yapar.
  • Sonraki iki satır veritabanına eşzamansız bir istekte bulunur ve dosyayı okur. Bekleme operatörü, veritabanından ve dosyadan eşzamansız okuma görevi tamamlanana kadar geçerli işlevi duraklatır.
  • Son satırlar sokete eşzamansız yazma işlemini gerçekleştirir, ancak bu yalnızca veritabanından ve dosyadan eşzamansız okumayı bekledikten sonra gerçekleşir.

Bu, sırayla önce veritabanını, sonra dosyayı beklemekten daha hızlıdır. Birçok uygulamada, async / wait'in performansı klasik geri çağırma işlevlerinden daha iyidir ve bu tür kodlar senkronize olarak okunur.

Eşyordamlar

Yukarıda açıklanan mekanizmaya eşyordam adı verilir. Sık sık "coroutine" varyantını duyabilirsiniz (İngilizce coroutine - coroutine'den).

Çoklu giriş noktaları

Temel olarak eşyordamlar birden fazla giriş ve çıkış noktasına sahip işlevlerdir. Normal fonksiyonların yalnızca bir giriş noktası ve birden fazla çıkış noktası vardır. Yukarıdaki örneğe dönersek, ilk giriş noktası, async operatörünü kullanan işlev çağrısının kendisi olacak, ardından işlev, veritabanını veya dosyayı beklemek yerine yürütülmesini kesecektir. Sonraki tüm beklemeler işlevi yeniden başlatmaz ancak önceki kesinti noktasında yürütmeye devam eder. Evet, birçok dilde bir eşyordamda birden fazla bekleme olabilir.

Daha iyi anlamak için Python'daki koda bakalım:

Def async_factorial(): result = 1 while True: verim sonuç sonuç *= i fac = async_factorial() for i in range(42): print(next(fac))

Program, 0'dan 41'e kadar olan sayılarla tüm faktöriyel sayı dizisinin çıktısını verecektir.

async_factorial() işlevi, next() işlevine aktarılabilecek bir jeneratör nesnesi döndürecektir; bu, tüm yerel işlev değişkenlerinin durumunu koruyarak bir sonraki verim ifadesine kadar eşyordamı yürütmeye devam edecektir. next() işlevi, eşyordam içindeki verim ifadesinin aktardığı şeyi döndürür. Yani async_factorial() fonksiyonu teorik olarak birden fazla giriş ve çıkış noktasına sahiptir.

Yığınlı ve Yığınsız

Yığın kullanımına bağlı olarak eşyordamlar, her bir eşyordamın kendi yığınına sahip olduğu yığınlı ve tüm yerel işlev değişkenlerinin özel bir nesnede depolandığı yığınsız olarak ikiye ayrılır.

Eşyordamlarda bir verim ifadesini herhangi bir yere koyabildiğimiz için, yığındaki çerçeve (yerel değişkenler) ve diğer meta bilgileri içeren, fonksiyonun tüm bağlamını bir yerde saklamamız gerekir. Bu, örneğin yığın dolu eşyordamlarda yapıldığı gibi yığının tamamen değiştirilmesiyle yapılabilir.

Aşağıdaki şekilde, eşzamansız çağrı yeni bir yığın çerçevesi oluşturur ve iş parçacığı yürütmesini buna geçirir. Bu pratikte yeni bir iş parçacığıdır, yalnızca ana iş parçacığıyla eşzamansız olarak yürütülecektir.

verim sırayla yürütülmek üzere önceki yığın çerçevesini döndürür ve önceki yığındaki geçerli olanın sonuna bir referans depolar.

Kendi yığınınıza sahip olmak, iç içe geçmiş işlev çağrılarından yararlanmanıza olanak tanır, ancak bu tür çağrılara, yığınsız eşyordamlardan daha yavaş olan program yürütme bağlamının tamamen oluşturulması/değiştirilmesi eşlik eder.

Yığınsız eşyordamlar daha üretken ama aynı zamanda daha sınırlı olanlardır. Yığını kullanmazlar ve derleyici, eşyordamları içeren bir işlevi, eşyordamları olmayan bir durum makinesine dönüştürür. Örneğin, kod:

Def fib(): a = 0 b = 1 while True: verim a a += b verim b b += a

Aşağıdaki sözde koda dönüştürülecek:

Sınıf fib: def __init__(self): self.a = 0 self.b = 1 self.__result: int self.__state = 0 def __next__(self): while True: if self.__state == 0: self.a = 0 self.b = 1 if self.__state == 0 veya self.__state == 3: self.__result = self.a self.__state = 1 return self.__result if self.__state == 1: self.a += self.b self.__result = self.b self.__state = 2 return self.__result if self.__state == 2: self.b += a self.__state = 3 break

Temel olarak bu, fonksiyonun tüm durumunu ve aynı zamanda verimin çağrıldığı son noktayı kaydeden bir sınıf yaratır. Bu yaklaşımda bir sorun var: verim yalnızca eşyordamlı bir işlevin gövdesi içinde çağrılabilir, ancak iç içe geçmiş işlevler içinden çağrılamaz.

Simetrik ve asimetrik

Koroutinler ayrıca simetrik ve asimetrik olarak ikiye ayrılır.

Simetrik Bekleyen tüm asenkron işlemler arasından bir sonraki yürütülmesi gerekeni seçen küresel bir eşyordam zamanlayıcıya sahip olun. Bunun bir örneği, wait_connection() işlevinin başında tartışılan zamanlayıcıdır.

İÇİNDE asimetrik eşyordamların global bir zamanlayıcısı yoktur ve programcı derleyici desteğiyle birlikte hangi eşyordamın ne zaman yürütüleceğini seçer. Çoğu eşyordam uygulaması asimetriktir.

Çözüm

Eşzamansız programlama, sık sistem beklemelerine neden olan yüksek yüklü programları optimize etmek için çok güçlü bir araçtır. Ancak herhangi bir karmaşık teknoloji gibi, sırf var olduğu için kullanılamaz. Kendinize her zaman şu soruyu sormalısınız: Bu teknolojiye ihtiyacım var mı? Bana ne gibi pratik faydalar sağlayacak? Aksi takdirde geliştiriciler herhangi bir kar elde etmeden çok fazla çaba, zaman ve para harcama riskiyle karşı karşıya kalırlar.

Dipnot: .NET Framework'te eşzamansız programlama. EndOperation, Pooling, Callback yöntemleri. Rastgele bir yöntemin eşzamansız başlatılması. Arayüz güncellemesi. Çok iş parçacıklı uygulamaların güvenliği. Senkronizasyon: otomatik, manuel; senkronizasyon alanlarının kullanımı. İlerleme Çubuğu kontrolü

"Uzun" bir yöntemi çalıştırdığımızda, işlemci onu yürütecek ve diğer kullanıcı isteklerini yerine getirmek için "zamanı kalmayacaktır" (Şekil 7.2).

Eşzamanlı olarak çalışırken, uygulamanın yalnızca bir iş parçacığı vardır. Kullanarak asenkron model Programlamada, birden fazla paralel iş parçacığını çalıştırabilir ve yeni kullanıcı eylemlerine, bunlar çalışırken tepki verebilirsiniz. N-iş parçacığı yürütüldüğünde, sonucu ekranda görüntülersiniz.

Günlük yaşamda asenkron davranışın pek çok örneği vardır; hammaddelerin bir depoda yenilenmesi, fabrikanın kesintisiz çalışmasını sağlamasına olanak tanır. Senkronize olmayan bir model, hammaddelerin tamamen kullanılması ve ardından malzemenin teslimatını beklerken aksama süresi olacaktır.

.NET Framework'te eşzamansız programlama desteği

Yerleşik desteğe sahip sınıflar asenkron model, senkronize yöntemlerin her biri için bir çift eşzamansız yönteme sahiptir. Bu yöntemler Begin ve End sözcükleriyle başlar. Örneğin System.IO.Stream sınıfının Read metodunun asenkron versiyonunu kullanmak istiyorsak aynı sınıfın BeginRead ve EndRead metodlarını kullanmamız gerekmektedir.

Yerleşik desteği kullanmak için asenkron model programlama için uygun BeginOperation yöntemini çağırmanız ve çağrı tamamlama modelini seçmeniz gerekir. BeginOperation yönteminin çağrılması, zaman uyumsuz işlemin yürütme durumunu belirleyen bir IAsyncResult arabirim nesnesini döndürür. Eşzamansız yöntemleri sonlandırmanın birkaç yolu vardır.

EndOperation yöntemi

EndOperation yöntemi, ana iş parçacığının, eşzamansız yöntem çağrısının sonuçlarından bağımsız olarak büyük miktarda hesaplama gerçekleştirmesi gerektiğinde, eşzamansız bir çağrıyı sonlandırmak için kullanılır. Ana iş tamamlandıktan ve uygulamanın daha sonraki işlemler için asenkron yöntemin sonuçlarına ihtiyaç duymasından sonra EndOperation yöntemi çağrılır. Bu durumda, asenkron yöntem tamamlanana kadar ana iş parçacığı askıya alınacaktır. Bu yöntemin kullanımına bir örnek:

Sistemin Kullanımı; System.IO kullanarak; namespace EndOperation ( class Class1 ( static void Main(string args) ( //Bir akış oluşturun ve dosyayı açın. FileStream fs = new FileStream("text.txt", FileMode.Open); bayt fileBytes = yeni bayt; // Çalıştır paralel bir iş parçacığında Okuma yöntemi.IAsyncResult ar = fs.BeginRead(fileBytes, 0, fileBytes.Length, null, null); for(int i = 0; i<10000000; i++) { // Имитация длительной работы основного // потока, не зависящая от выполнения асинхронного метода. } // После завершения работы основного потока // запускаем завершение выполнения параллельного // метода Read. fs.EndRead(ar); Console.Write(System.Text.Encoding.Default.GetString(fileBytes)); } } } Листинг 7.1.

Paralel iş parçacığının yürütülmesini sonlandırmak için burada EndRead yöntemi çağrıldı. Uzun vadeli çalışmayı simüle eden bir kod olarak, "Windows formlarında kod kitaplıklarını kullanma" bölümünde tartıştığımız tam görev tamamlama sayacını bağlayabilirsiniz.

Havuzlama yöntemi

Bu yöntem, eşzamansız bir yöntemin yürütülmesini kontrol etmeniz gereken durumlarda uygundur. Bunu kullanırken, IAsyncResult türündeki bir nesnenin IsCompleted özelliğini kullanarak eşzamansız yöntemin yürütülmesini manuel olarak kontrol etmeniz gerekir. Bu en yaygın sonlandırma tekniği değildir çünkü çoğu işlem yürütme kontrolü gerektirmez. Yoklama yöntemini kullanma örneği:

Sistemin Kullanımı; System.IO kullanarak; namespace Pooling ( class Class1 ( static void Main(string args) ( FileStream fs = new FileStream("text.txt", FileMode.Open); byte fileBytes = new byte; Console.Write("Read yöntemini eşzamansız olarak çalıştır.") ; // Okuma yöntemini eşzamansız olarak çalıştırın. IAsyncResult ar = fs.BeginRead(fileBytes, 0, fileBytes.Length, null, null); // Eşzamansız yöntemin yürütülmesini kontrol edin. while(!ar.IsCompleted) ( // While ekranda dosya okunduğunda şu mesaj görüntülenir: Console.Write("İşlem devam ediyor"); ) Console.WriteLine(); string textFromFile = System.Text.Encoding.Default.GetString(fileBytes); Console.Write(textFromFile); )) )) Listeleme 7.2.

Büyük olasılıkla, işlem sırasında yazının görünümünü fark etmeyeceksiniz - dosya çok hızlı okunacaktır. Bu yöntemi test etmek için diskete büyük bir metin dosyası yazın, adresi belirtin ve uygulamayı yeniden çalıştırmayı deneyin.

Geri arama yöntemi

Eşzamansız bir çağrıyı sonlandırmaya yönelik Geri Arama yöntemi, ana iş parçacığının engellenmesini önlemek istediğiniz durumlarda kullanılır. Callback kullanırken, paralel iş parçacığında çalışan yöntem tamamlandığında çağrılan yöntemin gövdesinde EndOperation yöntemini çalıştırıyoruz. Çağrılan yöntemin imzası, AsyncCallback temsilcisinin imzasıyla eşleşmelidir.

Geri Arama seçeneğinin kullanımına bir örnek:

Sistemin Kullanımı; System.IO kullanarak; namespace Geri çağırma ( class Class1 ( // Bir akış ve bir bayt dizisi oluşturun. static FileStream fs; statik bayt fileBytes; static void Main(string args) ( // Dosyayı bir akışa açın. fs = new FileStream("text. txt", FileMode.Open); fileBytes = yeni bayt; // WorkComplete yöntemini ileterek Read yöntemini eşzamansız olarak çalıştırın fs.BeginRead(fileBytes, 0, (int)fs.Length, new AsyncCallback(WorkComplete), null); ) // /

/// Paralel iş parçacığı sonlandırıldığında yöntem otomatik olarak çağrılır. /// /// IAsyncResult türü nesne. static void WorkComplete(IAsyncResult ar) ( // Yöntemin sonunu başlatın. fs.EndRead(ar); string textFromFile = System.Text.Encoding.Default.GetString(fileBytes); Console.Write(textFromFile); ) ) ) Listeleme 7.3.

Kitapla birlikte verilen diskte EndOperation, Pooling ve Callback uygulamalarını (Code\Glava7\EndOperation, Pooling, Callback) bulacaksınız.

Son güncelleme: 10/17/2018

Eşzamansızlık, bireysel görevleri ana iş parçacığından özel eşzamansız yöntemlere veya kod bloklarına taşımanıza olanak tanır. Bu, özellikle uzun görevlerin kullanıcı arayüzünü engelleyebileceği grafik programlarda geçerlidir. Bunun olmasını önlemek için eşzamansızlığı kullanmanız gerekir. Eşzamansızlığın web uygulamalarında kullanıcılardan gelen istekleri işlerken, veritabanlarına veya ağ kaynaklarına erişirken de faydaları vardır. Veritabanına yapılan büyük sorgular için, asenkron yöntem, veritabanından veri alana kadar bir süreliğine uykuya dalacak ve ana iş parçacığı çalışmasına devam edebilecektir. Senkronize bir uygulamada, veri almaya yönelik kod ana iş parçacığındaysa, bu iş parçacığı veri alırken basitçe bloke olur.

C#'ta eşzamansız çağrılarla çalışmanın anahtarı iki anahtar sözcüktür: async ve wait, bunların amacı eşzamansız kod yazmayı kolaylaştırmaktır. Eşzamansız bir yöntem oluşturmak için birlikte kullanılırlar.

Asenkron yöntem aşağıdaki özelliklere sahiptir:

    Yöntem başlığı eşzamansız değiştiriciyi kullanır

    Yöntem bir veya daha fazla bekleme ifadesi içeriyor

    Dönüş türü aşağıdakilerden biridir:

    • DeğerGörevi

Asenkron bir yöntem, normal yöntem gibi, herhangi bir sayıda parametreyi kullanabilir veya bunları hiç kullanmayabilir. Ancak eşzamansız bir yöntem, parametreleri out ve ref değiştiricileriyle tanımlayamaz.

Ayrıca, bir yöntemin tanımında belirtilen async kelimesinin, yöntemi otomatik olarak asenkron hale getirmediğini de belirtmekte fayda var. Basitçe yöntemin bir veya daha fazla bekleme ifadesi içerebileceğini belirtir.

Eşzamansız bir yöntem örneğine bakalım:

Sistemin Kullanımı; System.Threading'i kullanarak; System.Threading.Tasks'ı kullanma; namespace HelloApp ( class Program ( static void Factorial() ( int result = 1; for(int i = 1; i)<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { Console.WriteLine("Начало метода FactorialAsync"); // выполняется синхронно await Task.Run(()=>Faktöriyel()); // eşzamansız olarak çalıştırıldı Console.WriteLine("FactorialAsync yönteminin sonu"); ) static void Main(string args) ( FactorialAsync(); // asenkron yöntemin çağrılması Console.WriteLine("Bir sayı girin: "); int n = Int32.Parse(Console.ReadLine()); Console.WriteLine($ "Sayının karesi (n * n)'ye eşittir"); Console.Read(); )) ))

Burada öncelikle faktöriyel hesaplamanın alışılagelmiş yöntemi anlatılmaktadır. Uzun çalışma sürelerini simüle etmek için Thread.Sleep() yöntemini kullanarak 8 saniyelik bir gecikme kullanır. Geleneksel olarak bu, bazı işleri uzun bir süre boyunca gerçekleştiren bir yöntemdir. Ancak anlaşılmasını kolaylaştırmak için basitçe 6'nın faktöriyelini hesaplar.

Aynı zamanda eş zamanlı olmayan FactorialAsync() yöntemini de tanımlar. Eşzamansızdır çünkü tanımda dönüş türünün önünde eşzamansız değiştiricisi vardır, dönüş türü geçersizdir ve bekleme ifadesi yöntem gövdesinde tanımlanmıştır.

Bekleme ifadesi, eşzamansız olarak yürütülecek bir görevi belirtir. Bu durumda benzer bir görev faktöriyel fonksiyonun uygulanmasını temsil eder:

Bekleyin Task.Run(()=>Factory());

Söylenmeyen kurallara göre, asenkron yöntemler adına Async - FactorialAsync () sonekini kullanmak gelenekseldir, ancak prensipte bu gerekli değildir.

Faktöriyelin kendisini asenkron yöntem FactorialAsync'te elde ederiz. Eşzamansız değiştiriciyle bildirildiğinden ve wait anahtar sözcüğünün kullanımını içerdiğinden eşzamansızdır.

Ve Main yönteminde buna asenkron yöntem diyoruz.

Bakalım programın konsol çıktısı ne olacak:

FactorialAsync yönteminin başlangıcı Bir sayı girin: 7 Sayının karesi 49 Ana yöntemin sonu Faktöriyel 720 FactorialAsync yönteminin sonu

Burada neler olduğuna adım adım bakalım:

    Zaman uyumsuz FactorialAsync yöntemini çağıran Main yöntemi çalıştırılır.

    FactorialAsync yöntemi, wait ifadesine kadar eşzamanlı olarak yürütülmeye başlar.

    Bekleme ifadesi eşzamansız bir görevi çalıştırır Task.Run(()=>Factorial())

    Eşzamansız görev Task.Run(()=>Factorial()) çalışırken (ve oldukça uzun bir süre çalışabilir), kod yürütme, çağıran yönteme, yani Main yöntemine geri döner. Main yönteminde sayının karesini hesaplamak için bir sayı girmemiz istenecektir.

    Bu, eşzamansız yöntemlerin avantajıdır - oldukça uzun süre çalışabilen eşzamansız bir görev, Main yöntemini engellemez ve onunla çalışmaya, örneğin veri girme ve işlemeye devam edebiliriz.

    Asenkron görev yürütmesini tamamladığında (yukarıdaki örnekte bir sayının faktöriyelini hesaplamıştır), asenkron görevi çağıran asenkron FactorialAsync yöntemi çalışmaya devam eder.

Faktöriyel fonksiyon en iyi örnek olmayabilir, çünkü gerçekte bu durumda onu asenkron yapmanın bir anlamı yoktur. Ancak başka bir örneğe bakalım - bir dosyayı okuma ve yazma:

Sistemin Kullanımı; System.Threading'i kullanarak; System.Threading.Tasks'ı kullanma; System.IO kullanarak; namespace HelloApp ( class Program ( static async void ReadWriteAsync() ( string s = "Merhaba dünya! Her seferinde bir adım"; // hello.txt - (StreamWriterwriter = new StreamWriter(") kullanılarak yazılacak ve okunacak dosya merhaba .txt", false)) ( wait write.WriteLineAsync(s); // bir dosyaya eşzamansız yazma) kullanarak (StreamReader okuyucu = new StreamReader("hello.txt")) ( string result = wait okuyucu.ReadToEndAsync() ; / / Console.WriteLine(result); ) dosyasından eşzamansız okuma ) static void Main(string args) ( ReadWriteAsync(); Console.WriteLine("Bazı işler"); Console.Read(); ) ))

Eşzamansız ReadWriteAsync() yöntemi, bir dosyaya bir dize yazar ve ardından yazılı dosyayı okur. Bu tür işlemler, özellikle büyük miktarda veri söz konusu olduğunda uzun zaman alabilir, bu nedenle bu tür işlemleri eşzamansız olarak yapmak daha iyidir.

.NET çerçevesi bu tür işlemler için zaten yerleşik desteğe sahiptir. Örneğin StreamWriter sınıfı bir WriteLineAsync() yöntemini tanımlar. Aslında, zaten eşzamansız bir işlemi temsil ediyor ve bir dosyaya yazılması gereken belirli bir dizeyi parametre olarak alıyor. Bu yöntem eşzamansız bir işlemi temsil ettiğinden, bu yönteme yapılan çağrıyı bir bekleme ifadesi olarak çerçeveleyebiliriz:

Yazarı bekleyin.WriteLineAsync(s); // bir dosyaya eşzamansız yazma

Benzer şekilde StreamReader sınıfı, aynı zamanda eşzamansız bir işlemi temsil eden ve okunan tüm metni döndüren bir ReadToEndAsync() yöntemini tanımlar.

.NET Core çerçevesi birçok benzer yöntemi tanımlar. Kural olarak, dosyalarla çalışmak, ağ istekleri veya veritabanı sorguları göndermekle ilişkilendirilirler. Async sonekiyle kolayca tanınırlar. Yani, eğer bir yöntemin adında benzer bir son ek varsa, bu durumda bekleme ifadesinde kullanılması daha olasıdır.

Static void Main(string args) ( ReadWriteAsync(); Console.WriteLine("Bazı işler"); Console.Read(); )

Yine ReadWriteAsync metodundaki çalıştırma ilk wait ifadesine ulaştığında kontrol Main metoduna geri döner ve onunla çalışmaya devam edebiliriz. Dosyaya yazma ve dosyayı okuma paralel olarak yapılacak ve Main metodunun çalışmasına engel olmayacaktır.

Asenkron İşlem Tanımlama

Yukarıda belirtildiği gibi, .NET Core çerçevesi, eşzamansız bir işlemi temsil eden birçok yerleşik yönteme sahiptir. Async sonekiyle biterler. Ve bu tür yöntemleri çağırmadan önce wait operatörünü belirtebiliriz. Örneğin:

StreamWriter yazarı = new StreamWriter("hello.txt", false); yazarı bekliyoruz.WriteLineAsync("Merhaba"); // bir dosyaya eşzamansız yazma

Veya Task.Run() yöntemini kullanarak asenkron bir işlemi kendimiz tanımlayabiliriz:

Statik void Factorial() ( int result = 1; for (int i = 1; i<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync() { await Task.Run(()=>Faktöriyel()); // asenkron bir işlemin çağrılması)

Lambda ifadesini kullanarak eşzamansız bir işlemi tanımlayabilirsiniz:

Statik async void FactorialAsync() ( wait Task.Run(() => ( int result = 1; for (int i = 1; i)<= 6; i++) { result *= i; } Thread.Sleep(8000); Console.WriteLine($"Факториал равен {result}"); }); }

Parametreleri eşzamansız bir işleme geçirme

Yukarıda 6'nın faktöriyelini hesapladık ama diyelim ki farklı sayıların faktöriyelini hesaplamak istiyoruz:

Sistemin Kullanımı; System.Threading'i kullanarak; System.Threading.Tasks'ı kullanma; namespace HelloApp ( class Program ( static void Factorial(int n) ( int result = 1; for (int i = 1; i)<= n; i++) { result *= i; } Thread.Sleep(5000); Console.WriteLine($"Факториал равен {result}"); } // определение асинхронного метода static async void FactorialAsync(int n) { await Task.Run(()=>Faktöriyel(n)); ) static void Main(string args) ( FactorialAsync(5); FactorialAsync(6); Console.WriteLine("Bazı işler"); Console.Read(); ) ) )

Eşzamansız bir işlemden sonucun alınması

Eşzamansız bir işlem, normal bir yöntemi çağırırken elde ettiğimiz yöntemle elde edebileceğimiz bir sonuç döndürebilir:

Sistemin Kullanımı; System.Threading'i kullanarak; System.Threading.Tasks'ı kullanma; namespace HelloApp ( class Program ( static int Factorial(int n) ( int result = 1; for (int i = 1; i)<= n; i++) { result *= i; } return result; } // определение асинхронного метода static async void FactorialAsync(int n) { int x = await Task.Run(()=>Faktöriyel(n)); Console.WriteLine($"Faktöriyel (x)"); ) static void Main(string args) ( FactorialAsync(5); FactorialAsync(6); Console.Read(); )) ))

Faktöriyel yöntemi int türünde bir değer döndürür; bu değeri, asenkron bir işlemin sonucunu bu türdeki bir değişkene atayarak elde edebiliriz: int x = wait Task.Run(()=>Factorial(n));

Bir süredir asenkron programlamayla ilgili birçok soru alıyorum. Ve bu konunun birçok okuyucumun ilgisini çektiğini fark ederek, özellikle de bu terimleri açıklamak için bir makale yazmaya karar verdim. Asenkron programlama, modern İnternet'in çok önemli bir parçasıdır.

Öncelikle tamamen farklı iki kavramın olduğunu belirtmek gerekir: Birincisi senkron ve asenkron programlama modelleri, ve ikinci - tek iş parçacıklı ve çok iş parçacıklı ortamlar. Programlama modellerinin her biri (senkron ve asenkron) hem tek iş parçacıklı hem de çok iş parçacıklı ortamlarda çalışabilir.

Senkron programlama modeli.

Bu programlama modelinde bir iş parçacığı bir göreve atanır ve onun üzerinde çalışmaya başlar. Bir görev tamamlandığında, iş parçacığı bir sonraki görev için kullanılabilir. Onlar. bir görev sırayla bir başkasıyla değiştirilecektir. Bu modelde bir görevi yarıda bırakıp başka bir görevi tamamlamak mümkün değildir. Bu modelin tek iş parçacıklı ve çok iş parçacıklı ortamlarda nasıl çalıştığını tartışalım.

Tek İş Parçacıklı Ortam- yürütülmesi gereken birkaç görevimiz varsa ve mevcut sistem yalnızca bir iş parçacığı sağlıyorsa, görevler iş parçacığına tek tek atanır. Bu görsel olarak şu şekilde tasvir edilebilir:

Nerede Konu 1 - bir iplik, Görev 1 ve Görev 2, Görev 3, Görev 4 – ilgili görevler.

Bir akışımız olduğunu görüyoruz ( Konu 1) Ve dört görev bunun tamamlanması gerekiyor. Bir iş parçacığı görevler üzerinde çalışmaya başlar ve tüm görevleri tek tek tamamlar.

Çok iş parçacıklı ortam - Çok İş parçacıklı- bu ortamda bu görevleri aynı anda gerçekleştirebilecek birden fazla iş parçacığı kullanıyoruz. Bu şu anlama geliyor: iş parçacığı havuzu(mevcut kaynaklara göre gerektiğinde yeni başlıklar da oluşturulabilir) ve birden fazla görev.

Dört konumuzun ve aynı sayıda görevimizin olduğunu görüyoruz. Bu nedenle her iş parçacığı bir görevi yerine getirir ve onu tamamlar. Bu ideal bir senaryodur, ancak normal koşullar altında mevcut iş parçacığı sayısından daha fazla göreve sahip olma eğilimindeyiz. Ve böylece, bir iş parçacığı bir görevi yürütmeyi bitirdiğinde, hemen bir başkasını yürütmeye başlayacaktır. Ayrıca, CPU döngüleri ve bellek gibi sistem kaynaklarına ihtiyaç duyduğu için her seferinde yeni bir iş parçacığının oluşturulmadığını unutmayın; bu yeterli olmayabilir.

Şimdi asenkron modelden ve onun tek iş parçacıklı ve çok iş parçacıklı ortamlarda nasıl davrandığından bahsedelim.

Asenkron programlama modeli.

Senkronize programlama modelinden farklı olarak, burada belirli bir görevi başlatan bir iş parçacığı, mevcut durumunu korurken yürütmesini belirli bir süre durdurabilir ve başka bir görevi yürütmeye başlayabilir.

tüm görevlerin yerine getirilmesinden ve bunları birbirleriyle değiştirmekten bir iş parçacığının sorumlu olduğunu görüyoruz.

Eğer sistemimiz birden fazla thread oluşturabiliyorsa tüm threadler asenkron modelde çalışabilir.

Aynı görevlerin olduğunu görüyoruz. T4, T5, T6 birden fazla iş parçacığı tarafından işlenir. Bu senaryonun güzelliği ve gücü budur. Gördüğünüz gibi görev T4 Konunun ilki başlatıldı Konu 1 ve iş parçacığında tamamlandı Konu 2. Benzer T6 Biter Konu 2, Konu 3 ve Konu 4.

Toplamda dört senaryomuz var:

  • Senkron tek iş parçacığı
  • Senkron çok iş parçacıklı
  • Asenkron tek iş parçacığı
  • Asenkron çok iş parçacıklı

Asenkron Programlamanın Faydaları

Herhangi bir uygulama için iki şey önemlidir: kullanılabilirlik ve performans. Kullanılabilirlik önemlidir çünkü kullanıcı bazı verileri kaydetmek için bir düğmeye tıkladığında, dahili bir nesnedeki verileri okumak ve doldurmak, SQL sunucusuyla bağlantı kurmak ve sorguyu oraya kaydetmek gibi birkaç küçük görevin gerçekleştirilmesini gerektirir. . vesaire.

Çünkü SQL ServerÖrneğin, büyük olasılıkla ağdaki başka bir bilgisayarda çalışıyor ve farklı bir işlem altında çalışıyor; bu uzun zaman alabilir. Ve uygulama tek bir iş parçacığı üzerinde çalışıyorsa, kullanıcının cihaz ekranı, tüm görevler tamamlanana kadar etkin olmayan bir durumda kalacaktır; bu, çok kötü bir kullanıcı arayüzü örneğidir. Bu nedenle birçok uygulama ve yeni çerçeve, duyarlı bir arayüzü korurken birçok görevi gerçekleştirmenize olanak tanıdığı için tamamen eşzamansız modele dayanmaktadır.

Uygulamanın verimliliği de oldukça önemlidir. Bir sorgu yürütülürken zamanın yaklaşık %70-80'inin bağımlı görevleri beklerken kaybedildiği tahmin edilmektedir. Bu nedenle asenkron programlamanın kullanışlı olduğu yer burasıdır.

Bu nedenle, bu yazıda senkron ve asenkron programlamanın ne olduğuna baktık. Modern geliştirme araçlarının büyük çoğunluğunun temelini oluşturduğu için asenkron programlamaya özellikle vurgu yapıldı. Sonraki makalelerde asenkron modeli kullanan gerçek örneklerle tanışacağız.

Son güncelleme: 31.10.2015

Önceki konular, zaman uyumsuz ve bekleme anahtar sözcüklerini kullanarak zaman uyumsuzluğun kullanımını ele alıyordu. Ancak C#'ta eşzamansız çağrıların kullanıldığı bu modele ek olarak başka bir model daha vardır: eşzamansız delegelerin kullanımı. Eşzamansız delegeler, C#'ta eşzamansız ve bekleme özelliğinin kullanıma sunulmasından önce yaygın olarak kullanılıyordu, ancak artık eşzamansız ve bekleme, eşzamansız kod yazmayı çok daha kolay hale getiriyor. Ancak eşzamansız delegeler yine de kullanılabilir. Öyleyse onlara bakalım.

Eşzamansız delegeler, delegelerin işaret ettiği yöntemlerin eşzamansız olarak çağrılmasına olanak tanır. Temsilcilerle ilgili konuda, delegelerin Invoke yöntemi kullanılarak veya BeginInvoke/EndInvoke yöntem çifti kullanılarak eşzamansız olarak çağrılabileceği söylendi. Bir örneğe bakalım. Öncelikle uygulamamızda normal senkron kod kullanırsak ne olacağına bakalım:

Sistemin Kullanımı; System.Threading'i kullanarak; namespace AsyncApp ( class Program ( genel temsilci int DisplayHandler(); static void Main(string args) ( DisplayHandler handler = new DisplayHandler(Display); int result = handler.Invoke(); Console.WriteLine("Ana yöntem devam ediyor"); Console.WriteLine("Sonuç (0)", sonuç); Console.ReadLine(); ) static int Display() ( Console.WriteLine("Görüntüleme yöntemi çalışmaya başlar...."); int result = 0 ; for (int i = 1; i< 10; i++) { result += i * i; } Thread.Sleep(3000); Console.WriteLine("Завершается работа метода Display...."); return result; } } }

Bu, sayı döndüren parametresiz bir yöntemi referans olarak alan özel bir DisplayHandler temsilcisi oluşturur. Bu durumda bu yöntem, bazı işler yapan Görüntüleme yöntemidir. Bu durumda aşağıdaki gibi bir çıktı elde edeceğiz:

Display metodu çalışmaya başlar Display metodu biter Main metodu çalışmaya devam eder sonuç 285

Genel olarak bir temsilci kullanamaz ve Display yöntemini doğrudan çağıramazsınız. Ancak her durumda, çağrıldıktan sonra Main yönteminin çalışması, Display yönteminin yürütülmesi tamamlanana kadar daha da engellenir.

Şimdi eşzamansız temsilci çağrılarını kullanarak örneği değiştirelim:

Sistemin Kullanımı; System.Threading'i kullanarak; namespace AsyncApp ( class Program ( genel temsilci int DisplayHandler(); static void Main(string args) ( DisplayHandler handler = new DisplayHandler(Display); IAsyncResult resultObj = handler.BeginInvoke(null, null); Console.WriteLine("Yöntem devam ediyor Main"); int result = handler.EndInvoke(resultObj); Console.WriteLine("Sonuç (0)", sonuç); Console.ReadLine(); ) static int Display() ( Console.WriteLine("Yöntem) çalışmaya başlar Ekran......"); int result = 0; for (int i = 1; i< 10; i++) { result += i * i; } Thread.Sleep(3000); Console.WriteLine("Завершается работа метода Display...."); return result; } } }

Eylemlerin özü neredeyse hiç değişmeden kaldı, aynı Görüntüleme yöntemi, ancak şimdi BedinInvoke/EndInvoke yöntemleri kullanılarak eşzamansız olarak çağrılıyor. Şimdi biraz farklı bir çıktı elde edebiliriz:

Display metodu çalışmaya başlar Main metodu çalışmaya devam eder Display metodu biter sonuç 285 olur

Bu nedenle, Display yöntemini ifade işleyicisi aracılığıyla çağırdıktan sonra.BeginInvoke(null, null), Main yöntemi askıya alınmaz. Ve DisplayHandler temsilcisi aracılığıyla Display yönteminin yürütülmesi başka bir iş parçacığında gerçekleşir. Ve yalnızca Main yöntemindeki yürütme şu satıra ulaştığında int result = handler.EndInvoke(resultObj); Görüntüleme yönteminin yürütmeyi tamamlamasını engeller ve bekler.

Şimdi BeginInvoke ve EndInvoke yöntemlerinin ve IAsyncResult arayüzünün kullanım özelliklerine bakalım.