Zaman uyumsuz programlama
Bir ağdan veri isteme, veritabanına erişme veya bir dosya sistemini okuma ve yazma gibi bir I/O'ya bağlı ihtiyaç varsa, zaman uyumsuz programlama kullanmak istemeniz gerekir. Ayrıca, zaman uyumsuz kod yazmak için iyi bir senaryo olan pahalı bir hesaplama gerçekleştirme gibi CPU'ya bağlı kodunuz da olabilir.
C# dil düzeyinde bir zaman uyumsuz programlama modeline sahip olur. Bu model, geri çağırmaları juggle yapmak veya zaman uyumsuzluğu destekleyen bir kitapla uyumlu olmak zorunda kalmadan zaman uyumsuz kodu kolayca yazabilir. Görev Tabanlı Zaman Uyumsuz Desen (TAP) olarak bilinen şeyi izler.
Zaman uyumsuz modele genel bakış
Zaman uyumsuz programlamanın çekirdeği, Task zaman uyumsuz işlemleri model alan ve Task<T> nesneleridir. Bunlar ve anahtar sözcükleri async tarafından await de destekler. Model çoğu durumda oldukça basittir:
- I/O'ya bağlı kod için, bir yöntemin içinde veya
TaskdöndürenTask<T>bir işlemiasyncbeklersiniz. - CPU'ya bağlı kod için, yöntemiyle bir arka plan iş parçacığında başlatan bir işlemi Task.Run beklersiniz.
Anahtar await sözcüğü, sihrin olduğu yerdir. , gerçekleştirilen yöntemi çağırana denetim verir ve sonuç olarak kullanıcı arabiriminin yanıt vermesini veya bir await hizmetin esnek olmasını sağlar. ve dışında zaman uyumsuz koda yaklaşmanın yolları da vardır ancak bu makale dil async await düzeyindeki yapılara odaklanır.
I/O bağlı örneği: Web hizmetlerinden veri indirme
Bir düğmeye basıldığında ancak kullanıcı arabirimi iş parçacığını engellemek istemeyebilirsiniz. Şu şekilde gerçek olabilir:
private readonly HttpClient _httpClient = new HttpClient();
downloadButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI as the request
// from the web service is happening.
//
// The UI thread is now free to perform other work.
var stringData = await _httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};
Kod, nesnelerle etkileşimde bulunmak için çıkmaza girmeden amacı (verileri zaman uyumsuz olarak indirme) ifade Task eder.
CPU'ya bağlı örnek: Oyun için hesaplama gerçekleştirme
Bir düğmeye basıldığında ekranda birçok kişinin zarara neden olduğu bir mobil oyun yazıyoruz. Zarar hesaplaması yapmak pahalı olabilir ve bunu kullanıcı arabirimi iş parçacığında yapmak, hesaplama yapılırken oyunun duraklatılmış gibi görünmesine neden olabilir!
Bunu işlemenin en iyi yolu, kullanarak işi yapmak ve kullanarak sonucu bekleyen bir arka plan Task.Run iş parçacığı await başlatmaktır. Bu, iş yapılırken kullanıcı arabiriminin sorunsuz hissetmesini sağlar.
private DamageResult CalculateDamageDone()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
}
calculateButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
Bu kod düğmenin tıklama olayı amacını açıkça ifade eder, arka plan iş parçacığının el ile yönetilmesini gerektirmez ve engelleyici olmayan bir şekilde bunu yapar.
Ne olur?
Zaman uyumsuz işlemlerin söz konusu olduğu birçok hareketli parça vardır. ve'nin altında neler olduğunu merak ediyorsanız daha fazla bilgi için Task Task<T> Async ayrıntılı makalesine bakın.
C# tarafında, derleyici kodunuzu bir arka plan işi tamamlandığında bir yürütmenin elde edat ve yürütmeyi devam gibi şeyleri takip eder bir durum makinesine await dönüştüren.
Teorik olarak benimser, bu zaman uyumsuzluğun Promise Modeli'nin bir uygulamasıdır.
Anlamanın önemli parçaları
- Zaman uyumsuz kod hem G/Ç'ye bağlı hem de CPU'ya bağlı kod için kullanılabilir, ancak her senaryo için farklı şekilde kullanılabilir.
- Zaman uyumsuz
Task<T>Taskkod, arka planda yapılan işi modellemek için kullanılan yapılar olan ve kullanır. - anahtar
asyncsözcüğü, bir yöntemi zaman uyumsuz bir yönteme dönüştürarak gövdesinde anahtarawaitsözcüğünü kullanmana olanak sağlar. - Anahtar sözcük uygulandığında, çağıran yöntemi askıya alır ve bekleyen görev tamamlandıktan sonra denetimi yeniden
awaitçağırana geri iletir. awaityalnızca zaman uyumsuz bir yöntemin içinde kullanılabilir.
CPU'ya bağlı ve I/O'ya bağlı işi tanıma
Bu kılavuzun ilk iki örneği, ve'i I/O bağlı ve async await CPU'ya bağlı çalışma için nasıl kullanabileceğiniz gösterdi. Kodunuzun performansını büyük ölçüde etkileyene ve belirli yapıların yanlış kullanımına yol açabilecek bir durum olduğundan, bir işin ne zaman I/O'ya bağlı veya CPU'ya bağlı olduğunu belirleyebilirsiniz.
Kod yazmadan önce sormanız gereken iki soru vardır:
Kodunuz veritabanındaki veriler gibi bir şeyi "bekliyor" mu?
Yanıtınız "evet" ise, çalışmanız I/O'ya bağlı olur.
Kodunuz pahalı bir hesaplama gerçekleştirecek mi?
"Evet" yanıtını verdiysanız, çalışmanız CPU'ya bağlı olur.
Sahip olduğu iş I/O'ya bağlı ise, ve async olmadan await kullanın. Task.Run Görev Paralel Kitaplığını kullanmamanız gerekir. Bunun nedeni Async in Depth (Zaman Uyumsuz) içinde ayrıntılı olarak açıklandı.
Sahip olduğu iş CPU'ya bağlı ise ve yanıt hızına önem veriyorsanız, ve kullanın, ancak ile başka bir async iş await parçacığında işi ortaya çıkarabilirsiniz. Task.Run İş eşzamanlılık ve paralellik için uygunsa, Görev Paralel Kitaplığı'nın da kullanmayı göz önünde bulundurabilirsiniz.
Ayrıca, kodunuzun yürütülmesini her zaman ölçmeniz gerekir. Örneğin, birden çok iş parçacığı kullanımında bağlam anahtarlarının ek yüküne kıyasla CPU'ya bağlı çalışmanızı yeterince maliyetli olmayan bir durumda bulabilirsiniz. Her seçimin bir takası vardır ve durumunuz için doğru takası sizin seçmeniz gerekir.
Diğer örnekler
Aşağıdaki örneklerde C# ile zaman uyumsuz kod yazmanın çeşitli yolları verilmiştir. Bu senaryolar birkaç farklı senaryoyu kapsıyor olabilir.
Bir ağdan veri ayıklama
Bu kod parçacığı sayfasındaki giriş sayfasından HTML'yi indirir ve https://dotnetfoundation.org ".NET" dizesinin HTML'de kaç kez oluştuğu sayısını sayar. Bu ASP.NET gerçekleştiren ve s numarayı döndüren bir Web API denetleyicisi yöntemi tanımlamak için ASP.NET kullanır.
Not
Üretim kodunda HTML ayrıştırma yapmayı planlıyorsanız normal ifadeler kullanmayın. Bunun yerine ayrıştırma kitaplığı kullanın.
private readonly HttpClient _httpClient = new HttpClient();
[HttpGet, Route("DotNetCount")]
public async Task<int> GetDotNetCount()
{
// Suspends GetDotNetCount() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await _httpClient.GetStringAsync("https://dotnetfoundation.org");
return Regex.Matches(html, @"\.NET").Count;
}
Burada, Bir Düğmeye basıldığında aynı görevi gerçekleştiren Universal Windows Uygulaması için yazılmış senaryo aşağıdaki gibidir:
private readonly HttpClient _httpClient = new HttpClient();
private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs e)
{
// Capture the task handle here so we can await the background task later.
var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");
// Any other work on the UI thread can be done here, such as enabling a Progress Bar.
// This is important to do here, before the "await" call, so that the user
// sees the progress bar before execution of this method is yielded.
NetworkProgressBar.IsEnabled = true;
NetworkProgressBar.Visibility = Visibility.Visible;
// The await operator suspends OnSeeTheDotNetsButtonClick(), returning control to its caller.
// This is what allows the app to be responsive and not block the UI thread.
var html = await getDotNetFoundationHtmlTask;
int count = Regex.Matches(html, @"\.NET").Count;
DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";
NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}
Birden çok görev tamamlandıktan sonra bekleyin
Kendinizi birden çok veri parçasının eşzamanlı olarak almak zorunda olduğu bir durumda bulabilirsiniz. API, birden çok arka plan işi üzerinde engelleyici olmayan bir bekleme işlemi gerçekleştiren zaman uyumsuz kod yazmanız için ve Task olmak için iki yöntem Task.WhenAll Task.WhenAny içerir.
Bu örnekte, bir kümenin User verilerini nasıl alayabilirsiniz? userId
public async Task<User> GetUserAsync(int userId)
{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
}
public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
var getUserTasks = new List<Task<User>>();
foreach (int userId in userIds)
{
getUserTasks.Add(GetUserAsync(userId));
}
return await Task.WhenAll(getUserTasks);
}
LinQ kullanarak bunu daha kısa bir şekilde yazmanın başka bir yolu da şu şekildedir:
public async Task<User> GetUserAsync(int userId)
{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
}
public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id));
return await Task.WhenAll(getUserTasks);
}
Daha az kod olsa da LINQ'u zaman uyumsuz kodla karıştırarak dikkatli olun. LINQ ertelenmiş (yavaş) yürütme kullandığı için, oluşturulan diziyi veya çağrısıyla tekrar yapmaya zorlamadıkça zaman uyumsuz çağrılar döngüde olduğu gibi hemen foreach .ToList() .ToArray() olmaz.
Önemli bilgiler ve öneriler
Zaman uyumsuz programlamada, beklenmeyen davranışları önleyen bazı ayrıntıları göz önüne alamayı unutmayın.
asyncmetotların birawaitgövdelerinde anahtar sözcük yoksa hiçbir zaman sonuç vermiyr!Bu, göz göre göre önemlidir. Bir yöntemin gövdesinde kullanılmazsa, C# derleyicisi bir uyarı üretir, ancak kod derler ve normal bir yöntem gibi
awaitasyncçalışır. Zaman uyumsuz yöntem için C# derleyicisi tarafından oluşturulan durum makinesi herhangi bir şey gerçekleştirmeylediği için bu son derece verimsizdir.Yazmanız her zaman uyumsuz yöntem adının soneki olarak "Async" ekleyin.
Bu, zaman uyumlu ve zaman uyumsuz yöntemleri daha kolay ayırt etmek için .NET'te kullanılan kuraldır. Kodunuz tarafından açıkça çağrılmay belirli yöntemler (olay işleyicileri veya web denetleyicisi yöntemleri gibi) zorunlu değildir. Bunlar kodunuz tarafından açıkça çağrılmaması nedeniyle, adlandırmaları hakkında açık olmak o kadar önemli değildir.
async voidyalnızca olay işleyicileri için kullanılmalıdır.async void, zaman uyumsuz olay işleyicilerinin çalışmasına izin vermenin tek yolu, olayların dönüş türlerine sahip değildir (bu nedenle veTaskTask<T>kullanılamaz). Diğer kullanımlar TAP modelini izlemez ve kullanımı zorasync voidolabilir, örneğin:- Bir yöntemde atılan
async voidözel durumlar bu yöntemin dışında yakalanamaz. async voidyöntemleri test etmek zordur.async voidmetotları, çağıranın zaman uyumsuz olması beklenmiyorsa kötü yan etkilere neden olabilir.
- Bir yöntemde atılan
LINQ ifadelerinde zaman uyumsuz lambdalar kullanırken dikkatli bir şekilde dikkatli olun
LINQ'te lambda ifadeleri ertelenmiş yürütmeyi kullanır; başka bir anlama gelir; başka bir anlama gelir; kodun yürütülmesini beklemeyebilirsiniz. Engelleme görevlerinin bu girişe girişleri, doğru yazıldığı zaman kilitlenmeye neden olabilir. Buna ek olarak, zaman uyumsuz kodun bu şekilde iç içe yerleştirmesi, kodun yürütülmesini gerekçelendirerek zorlaştırabilir. Zaman uyumsuz ve LINQ güçlü ancak mümkün olduğunca dikkatli ve net bir şekilde birlikte kullanılmalıdır.
Görevleri engelleyici olmayan bir şekilde bekleyen kod yazma
Geçerli iş parçacığının tamamlandıktan sonra tamamlanmayacak şekilde engellenmesi kilitlenmelere ve engellenen bağlam iş parçacıklarına neden olabilir ve daha karmaşık hata
Taskişlemesi gerektirebilirsiniz. Aşağıdaki tabloda, görevlerin engelleyici olmayan bir şekilde bekletilenlerle nasıl başa baş etmek için yol gösterici bilgiler yer alır:Bunu kullanın... Bunun yerine... Bunu yapmak isterseniz... awaitTask.WaitveyaTask.ResultArka plan görevinin sonucu alınıyor await Task.WhenAnyTask.WaitAnyHerhangi bir görevin tamamlanacak şekilde beklemesi await Task.WhenAllTask.WaitAllTüm görevlerin tamamlanacak şekilde beklemesi await Task.DelayThread.SleepBir süre bekleme Kullanmayı düşünün
ValueTaskmümkün olduğuncaZaman
Taskuyumsuz yöntemlerden nesne döndürerek belirli yollarda performans sorunlarına yol açabilirsiniz.Taskbir başvuru t türü olduğu için bunu kullanmak bir nesneyi olduğu anlamına gelir. Değiştiriciyle bildirilen bir yöntemin önbelleğe alınmış bir sonuç döndüren veya zaman uyumlu olarak tamamlayan durumlarda, ek ayırmalar kodun performans açısından kritik bölümlerinde önemli birasynczaman maliyeti haline gelir. Bu ayırmalar sıkı döngülerde gerçekleşirse maliyetli olabilir. Daha fazla bilgi için bkz. genelleştirilmiş zaman uyumsuz dönüş türleri.Kullanmayı düşünün
ConfigureAwait(false)Yaygın bir soru, "yöntemini ne zaman Task.ConfigureAwait(Boolean) kullanlı?" şeklindedir. yöntemi, örneğin bekleyen
Tasksunucularını yapılandırmasını sağlar. Bu önemli bir konudur ve hatalı bir şekilde ayarlanmışsa performans üzerinde olası etkileri ve hatta kilitlenmeler olabilir. hakkında daha fazla bilgi içinConfigureAwaitbkz. ConfigureAwait SSS.Daha az durumlu kod yazma
Genel nesnelerin durumuna veya belirli yöntemlerin yürütülmesine bağlı değildir. Bunun yerine, yalnızca yöntemlerin dönüş değerlerine bağımlıdır. Neden?
- Kod için gerekçe daha kolay olacaktır.
- Kodu test etmek daha kolay olacaktır.
- Zaman uyumsuz ve zaman uyumlu kodu karıştırmak çok daha kolaydır.
- Yarış koşulları genellikle tamamen engel olabilir.
- Dönüş değerlerine bağlı olarak zaman uyumsuz kodun koordinesi basit olur.
- (Ek) Bağımlılık ekleme ile gerçekten iyi çalışır.
Önerilen hedef, kodunda tam veya neredeyse eksiksiz Bilgi Saydamlığı elde etmektir. Bunu yapmak tahmin edilebilir, test edilebilir ve korunabilir bir kod tabanına neden olur.
Diğer kaynaklar
- Zaman uyumsuz ayrıntılı, Görevlerin nasıl çalışması hakkında daha fazla bilgi sağlar.
- Görev zaman uyumsuz programlama modeli (C#).
- Zaman uyumsuz programlama için İpuçları Alan Wischik'in Six Essential İpuçları kaynağı.