Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Kodunuz ağ veri isteklerini, veritabanı erişimini veya dosya sistemi okuma/yazmalarını desteklemek için G/Ç'ye bağlı senaryolar uyguluyorsa, zaman uyumsuz programlama en iyi yaklaşımdır. Ayrıca, pahalı hesaplamalar gibi CPU'ya bağlı senaryolar için zaman uyumsuz kod da yazabilirsiniz.
C#, geri çağrılarla uğraşmak veya asenkronizmi destekleyen bir kütüphaneye uymak zorunda kalmadan kolayca asenkron kod yazmanızı sağlayan dil düzeyinde bir asenkron programlama modeline sahiptir. Model, Görev tabanlı zaman uyumsuz desen (TAP)olarak bilinen modeli izler.
Zaman uyumsuz programlama modelini keşfetme
Task
ve Task<T>
nesneleri zaman uyumsuz programlamanın çekirdeğini temsil eder. Bu nesneler, async
ve await
anahtar sözcükleri destekleyerek zaman uyumsuz işlemleri modellemek için kullanılır. Çoğu durumda model hem G/Ç hem de CPU'ya bağlı senaryolar için oldukça basittir. Bir async
yönteminin içinde:
-
G/Ç ile ilişkili kod,
Task
yöntemindeki birTask<T>
veyaasync
nesnesiyle temsil edilen bir işlemi başlatır. - CPU'ya bağlı kodTask.Run yöntemiyle bir arka plan iş parçacığı üzerinde bir işlem başlatır.
Her iki durumda da etkin bir Task
, tamamlanmamış olabilir bir zaman uyumsuz işlemi temsil eder.
Anahtar await
sözcük, sihrin gerçekleştiği yerdir.
await
ifadesini içeren yöntemi çağırana denetim verir ve sonuçta kullanıcı arabiriminin yanıt vermesine veya bir hizmetin esnek olmasına izin verir.
olsa da, bu makale dil düzeyindeki yapılara odaklanır.
Not
Bu makalede sunulan bazı örnekler, web hizmetinden veri indirmek için System.Net.Http.HttpClient sınıfını kullanır. Örnek kodda, s_httpClient
nesnesi Program
sınıfı türünde statik bir alandır:
private static readonly HttpClient s_httpClient = new();
Daha fazla bilgi için bu makalenin sonundaki tam örnek kod bakın.
Temel kavramları gözden geçirme
C# kodunuzda zaman uyumsuz programlama uyguladığınızda, derleyici programınızı bir durum makinesine dönüştürür. Bu yapı, kod bir await
ifadeye ulaştığında yürütmeyi duraklatma ve arka plan işi tamamlandığında yürütmeye devam etme gibi kodunuzda çeşitli işlemleri ve durumu takip eder.
Bilgisayar bilimi teorisi açısından, zaman uyumsuz programlama, Promise modelininzaman uyumsuzluk içerisindeki bir uygulamasıdır.
Zaman uyumsuz programlama modelinde, anlaşılması gereken birkaç temel kavram vardır:
- Hem G/Ç hem de CPU'ya bağlı kod için zaman uyumsuz kod kullanabilirsiniz, ancak uygulama farklıdır.
- Zaman uyumsuz kod, arka planda çalışmayı modellemek için
Task<T>
veTask
nesnelerini yapılar olarak kullanır. -
async
anahtar sözcüğü, yöntem gövdesindeawait
anahtar sözcüğünü kullanmanıza olanak tanıyan bir yöntemi zaman uyumsuz bir yöntem olarak bildirir. -
await
anahtar sözcüğünü uyguladığınızda kod çağırma yöntemini askıya alır ve görev tamamlanana kadar denetimi çağırana geri verir. -
await
ifadesini yalnızca zaman uyumsuz bir yöntemde kullanabilirsiniz.
G/Ç ile sınırlı örnek: Web hizmetinden veri indirme
Bu örnekte, kullanıcı bir düğme seçtiğinde uygulama bir web hizmetinden veri indirir. İndirme işlemi sırasında uygulamanın kullanıcı arabirimi iş parçacığını engellemek istemezsiniz. Aşağıdaki kod bu görevi gerçekleştirir:
s_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 s_httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};
Kod, Task
nesneleriyle etkileşimde karmaşıklaşmadan amacı (verileri zaman uyumsuz olarak indirme) ifade eder.
CPU'ya bağlı örnek: Oyun hesaplaması çalıştırma
Sonraki örnekte, bir mobil oyun, bir düğme olayına yanıt olarak ekrandaki birkaç karaktere hasar verir. Hasar hesaplaması yapmak pahalı olabilir. Kullanıcı arabirimi iş parçacığında hesaplamanın çalıştırılması, hesaplama sırasında görüntüleme ve kullanıcı arabirimi etkileşimi sorunlarına neden olabilir.
Görevi işlemenin en iyi yolu, çalışmayı Task.Run
yöntemiyle tamamlamak için bir arka plan işlem başlatmaktır. İşlem, bir await
ifadesi kullanılarak sonuç verir. Görev tamamlandığında işlem sürdürülür. Bu yaklaşım, iş arka planda tamamlarken kullanıcı arabiriminin sorunsuz çalışmasını sağlar.
static DamageResult CalculateDamageDone()
{
return new DamageResult()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
};
}
s_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);
};
Kod, düğme Clicked
olayının amacını açıkça ifade eder. Arka plan iş parçacığını manuel olarak yönetmek gerekmez ve görevi engellemeyen bir biçimde tamamlar.
CPU bağlı ve Girdi/Çıktı bağlı senaryoları tanıma
Önceki örneklerde G/Ç ve CPU'ya bağlı işler için async
değiştirici ve await
ifadesinin nasıl kullanılacağı gösterilmektedir. Her senaryo için bir örnek, işlemin bağlı olduğu yere göre kodun nasıl farklı olduğunu gösterir. Uygulamanıza hazırlanmak için bir işlemin G/Ç veya CPU'ya bağlı olduğunu belirlemeyi anlamanız gerekir. Uygulama seçiminiz kodunuzun performansını büyük ölçüde etkileyebilir ve olası olarak yapıların yanlış kullanılıp kullanılmamasına neden olabilir.
Kod yazmadan önce ele alınması gereken iki birincil soru vardır:
Soru | Senaryo | Uygulama |
---|---|---|
Kod, veritabanındaki veriler gibi bir sonucu veya eylemi beklemeli mi? | G/Ç bağlı |
async değiştiriciyi ve await ifade olmadan kullanın. Görev Paralel Kitaplığı'nı kullanmaktan kaçının. |
Kod pahalı bir hesaplama çalıştırmalıdır? | CPU'ya bağlı |
async değiştiricisini ve await ifadesini kullanın, ancak işi Task.Run yöntemiyle başka bir iş parçacığına aktarın. Bu yaklaşım, CPU yanıt hızıyla ilgili endişeleri ele alır. Çalışma eşzamanlılık ve paralellik için uygunsa, Görev Paralel Kitaplığı'nı da kullanmayı göz önünde bulundurun. |
Kodunuzun yürütülmesini her zaman ölçün. Çok iş parçacıklı çalışma sırasında, bağlam değişimlerinin maliyetine kıyasla CPU'ya bağlı çalışmanızın yeterince maliyetli olmadığını fark edebilirsiniz. Her seçimin dezavantajları vardır. Durumunuz için doğru dengeyi seçin.
Diğer örnekleri keşfedin
Bu bölümdeki örneklerde, C# dilinde zaman uyumsuz kod yazmanın çeşitli yolları gösterilmektedir. Karşılaşabileceğiniz birkaç senaryo ele alınıyor.
Ağdan veri ayıklama
Aşağıdaki kod, belirli bir URL'den HTML'yi indirir ve ".NET" dizesinin HTML'de kaç kez gerçekleştiğini sayar. Kod, görevi gerçekleştiren ve sayıyı 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 ifadeleri kullanmayın. Bunun yerine ayrıştırma kütüphanesi kullanın.
[HttpGet, Route("DotNetCount")]
static public async Task<int> GetDotNetCount(string URL)
{
// Suspends GetDotNetCount() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await s_httpClient.GetStringAsync(URL);
return Regex.Matches(html, @"\.NET").Count;
}
Evrensel Windows Uygulaması için benzer kod yazabilir ve bir düğmeye bastıktan sonra sayma görevini gerçekleştirebilirsiniz:
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.
// It's important to do the extra work here before the "await" call,
// so 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 action 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 tamamlanmasını bekleyin
Bazı senaryolarda kodun birden çok veri parçasını eşzamanlı olarak alması gerekir.
Task
API'leri, birden çok arka plan işi üzerinde engellemesiz bekleme gerçekleştiren zaman uyumsuz kod yazmanızı sağlayan yöntemler sağlar:
- Task.WhenAll yöntemi
- Task.WhenAny yöntemi
Aşağıdaki örnekte, bir dizi User
nesnesi için userId
nesne verilerini nasıl alabileceğiniz gösterilmektedir.
private static 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.
return await Task.FromResult(new User() { id = userId });
}
private 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 bu kodu daha kısa yazabilirsiniz:
private static async Task<User[]> GetUsersAsyncByLINQ(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();
return await Task.WhenAll(getUserTasks);
}
LINQ kullanarak daha az kod yazmanıza rağmen LINQ'yi zaman uyumsuz kodla karıştırırken dikkatli olun. LINQ ertelenen (veya gecikmeli) yürütmeyi kullanır. Zaman uyumsuz çağrılar, foreach
veya .ToList()
yöntemine yapılan bir çağrıyla oluşturulan sırayı yinelemeye zorlamadığınız takdirde, .ToArray()
döngüsünde olduğu gibi hemen gerçekleşmez. Bu örnekte sorguyu hevesle gerçekleştirmek ve sonuçları bir dizide depolamak için Enumerable.ToArray yöntemi kullanılır. Bu yaklaşım, id => GetUserAsync(id)
deyimini çalıştırıp görevi başlatmaya zorlar.
Zaman uyumsuz programlama için dikkat edilmesi gereken noktaları gözden geçirin
Zaman uyumsuz programlamada, beklenmeyen davranışları önleyebilecek birkaç ayrıntı göz önünde bulundurulması gerekir.
async() yöntemi gövdesi içinde await komutunu kullanın
async
değiştiricisini kullandığınızda, yöntem gövdesine bir veya daha fazla await
ifadesi eklemeniz gerekir. Derleyici bir await
ifadesiyle karşılaşmazsa, yöntem sonuç vermez. Derleyici bir uyarı oluştursa da, kod yine de derlenir ve derleyici yöntemini çalıştırır. C# derleyicisi tarafından zaman uyumsuz yöntem için oluşturulan durum makinesi hiçbir şey başarmadığından tüm işlem son derece verimsizdir.
Asenkron yöntem adlarına "Async" son ekini ekleyin.
.NET stil kuralı, tüm zaman uyumsuz yöntem adlarına "Async" sonekini eklemektir. Bu yaklaşım, zaman uyumlu ve zaman uyumsuz yöntemler arasında daha kolay ayrım yapmaya yardımcı olur. Kodunuz tarafından açıkça çağrılmamış bazı yöntemler (olay işleyicileri veya web denetleyicisi yöntemleri gibi) bu senaryoda geçerli olmayabilir. Bu öğeler kodunuz tarafından açıkça çağrılmadığından, açık adlandırma kullanmak o kadar önemli değildir.
Yalnızca olay işleyicilerinden 'async void' döndür
Olay işleyicilerinin void
dönüş türleri bildirmesi gerekir ve diğer yöntemler gibi Task
ve Task<T>
nesneleri kullanamaz veya döndüremez. Zaman uyumsuz olay işleyicileri yazarken, işleyiciler için async
dönüş yönteminde void
değiştiricisini kullanmanız gerekir.
async void
dönüş yöntemlerinin diğer uygulamaları TAP modelini izlemez ve zorluklar sunabilir:
-
async void
yöntemi içinde oluşan istisnalar bu yöntemin dışında yakalanamaz -
async void
yöntemleri test etmek zordur -
async void
yöntemleri, çağıran bunların eşzamansız olmasını beklemiyorsa negatif yan etkilere neden olabilir.
LINQ'te zaman uyumsuz lambdalarla dikkatli olun
LINQ ifadelerinde zaman uyumsuz lambdalar uygularken dikkatli olmanız önemlidir. LINQ'teki Lambda ifadeleri ertelenen yürütmeyi kullanır, bu da kodun beklenmeyen bir zamanda yürütülebileceği anlamına gelir. Bu senaryoya engelleyici görevlerin eklenmesi, kod doğru yazılmıyorsa kolayca kilitlenmeye neden olabilir. Ayrıca, zaman uyumsuz kodun iç içe yerleştirilmiş olması da kodun yürütülmesiyle ilgili mantık yürütmeyi zorlaştırabilir. Asenkron ve LINQ güçlüdür, ancak bu teknikler mümkün olduğunca dikkatli ve açık şekilde birlikte kullanılmalıdır.
Bloklamadan görevler için kontrolü devretmek
Programınız bir görevin sonucuna ihtiyaç duyuyorsa, await
ifadesini engelleyici olmayan bir şekilde uygulayan kod yazın. Geçerli iş parçacığını bir Task
öğesinin tamamlanmasını senkron olarak beklemek için engellemek, kilitlenmelere ve engellenen bağlam iş parçacıklarına neden olabilir. Bu programlama yaklaşımı daha karmaşık hata işleme gerektirebilir. Aşağıdaki tabloda, erişimin engelleyici olmayan bir yolla görevlerden nasıl sonuç alınıyor olduğu konusunda rehberlik sağlanmaktadır:
Görev senaryosu | Geçerli kod | 'await' ile değiştirin |
---|---|---|
Arka plan görev sonucunu alma |
Task.Wait veya Task.Result |
await |
Herhangi bir görev tamamlandığında devam et | Task.WaitAny |
await Task.WhenAny |
Tüm görevler tamamlandığında devam | Task.WaitAll |
await Task.WhenAll |
Bir süre sonra devam | Thread.Sleep |
await Task.Delay |
ValueTask türünü kullanmayı göz önünde bulundurun
Zaman uyumsuz bir yöntem Task
nesnesi döndürdüğünde, belirli yollarda performans sorunları ortaya çıkabilir.
Task
bir başvuru türü olduğundan, yığından bir Task
nesnesi ayrılır.
async
değiştiricisi ile bildirilen bir yöntem önbelleğe alınmış bir sonuç döndürürse veya zaman uyumlu olarak tamamlanırsa, ek ayırmalar kodun performans açısından kritik bölümlerinde önemli zaman maliyetleri tahakkuk edebilir. Ayırmalar sıkı döngülerde gerçekleştiğinde bu senaryo maliyetli hale gelebilir. Daha fazla bilgi için genelleştirilmiş zaman uyumsuz dönüş türleri bölümüne bakın.
ConfigureAwait(false) özelliğinin ne zaman ayarlandığını anlama
Geliştiriciler genellikle Task.ConfigureAwait(Boolean) boole değerinin ne zaman kullanılacağını sorgular. Bu API, Task
örneğinin herhangi bir await
ifadesini uygulayan durum makinesine bağlamı yapılandırmasına olanak tanır. Boole değeri doğru ayarlanmadığında performans düşebilir veya kilitlenmeler oluşabilir. Daha fazla bilgi için bkz. ConfigureAwait SSS.
Daha az durum odaklı kod yaz
Genel nesnelerin durumuna veya belirli yöntemlerin yürütülmesine bağlı kod yazmaktan kaçının. Bunun yerine, yalnızca yöntemlerin dönüş değerlerine bağlıdır. Daha az durumsal olan kod yazmanın birçok avantajı vardır.
- Kod hakkında daha kolay mantık yürütme
- Kodu test etmek daha kolay
- Eşzamansız ve eşzamanlı kodu karıştırmak daha kolay
- Kodda yarış durumlarından kaçınmak mümkün
- Dönüş değerlerine bağlı zaman uyumsuz kodu koordine etmek basit
- (Bonus) Koda bağımlılık ekleme ile iyi çalışır
Önerilen bir hedef, kodunuzda eksiksiz veya neredeyse eksiksiz Referans Şeffaflığı sağlamaktır. Bu yaklaşım tahmin edilebilir, test edilebilir ve sürdürülebilir bir kod tabanına neden olur.
Tüm örneği gözden geçirin
Aşağıdaki kod, Program.cs örnek dosyasında bulunan tam örneği temsil eder.
using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.AspNetCore.Mvc;
class Button
{
public Func<object, object, Task>? Clicked
{
get;
internal set;
}
}
class DamageResult
{
public int Damage
{
get { return 0; }
}
}
class User
{
public bool isEnabled
{
get;
set;
}
public int id
{
get;
set;
}
}
public class Program
{
private static readonly Button s_downloadButton = new();
private static readonly Button s_calculateButton = new();
private static readonly HttpClient s_httpClient = new();
private static readonly IEnumerable<string> s_urlList = new string[]
{
"https://learn.microsoft.com",
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/azure",
"https://learn.microsoft.com/azure/devops",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dotnet/desktop/wpf/get-started/create-app-visual-studio",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/shows/net-core-101/what-is-net",
"https://learn.microsoft.com/enterprise-mobility-security",
"https://learn.microsoft.com/gaming",
"https://learn.microsoft.com/graph",
"https://learn.microsoft.com/microsoft-365",
"https://learn.microsoft.com/office",
"https://learn.microsoft.com/powershell",
"https://learn.microsoft.com/sql",
"https://learn.microsoft.com/surface",
"https://dotnetfoundation.org",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/maui"
};
private static void Calculate()
{
// <PerformGameCalculation>
static DamageResult CalculateDamageDone()
{
return new DamageResult()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
};
}
s_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);
};
// </PerformGameCalculation>
}
private static void DisplayDamage(DamageResult damage)
{
Console.WriteLine(damage.Damage);
}
private static void Download(string URL)
{
// <UnblockingDownload>
s_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 s_httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};
// </UnblockingDownload>
}
private static void DoSomethingWithData(object stringData)
{
Console.WriteLine($"Displaying data: {stringData}");
}
// <GetUsersForDataset>
private static 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.
return await Task.FromResult(new User() { id = userId });
}
private 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);
}
// </GetUsersForDataset>
// <GetUsersForDatasetByLINQ>
private static async Task<User[]> GetUsersAsyncByLINQ(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();
return await Task.WhenAll(getUserTasks);
}
// </GetUsersForDatasetByLINQ>
// <ExtractDataFromNetwork>
[HttpGet, Route("DotNetCount")]
static public async Task<int> GetDotNetCount(string URL)
{
// Suspends GetDotNetCount() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await s_httpClient.GetStringAsync(URL);
return Regex.Matches(html, @"\.NET").Count;
}
// </ExtractDataFromNetwork>
static async Task Main()
{
Console.WriteLine("Application started.");
Console.WriteLine("Counting '.NET' phrase in websites...");
int total = 0;
foreach (string url in s_urlList)
{
var result = await GetDotNetCount(url);
Console.WriteLine($"{url}: {result}");
total += result;
}
Console.WriteLine("Total: " + total);
Console.WriteLine("Retrieving User objects with list of IDs...");
IEnumerable<int> ids = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var users = await GetUsersAsync(ids);
foreach (User? user in users)
{
Console.WriteLine($"{user.id}: isEnabled={user.isEnabled}");
}
Console.WriteLine("Application ending.");
}
}
// Example output:
//
// Application started.
// Counting '.NET' phrase in websites...
// https://learn.microsoft.com: 0
// https://learn.microsoft.com/aspnet/core: 57
// https://learn.microsoft.com/azure: 1
// https://learn.microsoft.com/azure/devops: 2
// https://learn.microsoft.com/dotnet: 83
// https://learn.microsoft.com/dotnet/desktop/wpf/get-started/create-app-visual-studio: 31
// https://learn.microsoft.com/education: 0
// https://learn.microsoft.com/shows/net-core-101/what-is-net: 42
// https://learn.microsoft.com/enterprise-mobility-security: 0
// https://learn.microsoft.com/gaming: 0
// https://learn.microsoft.com/graph: 0
// https://learn.microsoft.com/microsoft-365: 0
// https://learn.microsoft.com/office: 0
// https://learn.microsoft.com/powershell: 0
// https://learn.microsoft.com/sql: 0
// https://learn.microsoft.com/surface: 0
// https://dotnetfoundation.org: 16
// https://learn.microsoft.com/visualstudio: 0
// https://learn.microsoft.com/windows: 0
// https://learn.microsoft.com/maui: 6
// Total: 238
// Retrieving User objects with list of IDs...
// 1: isEnabled= False
// 2: isEnabled= False
// 3: isEnabled= False
// 4: isEnabled= False
// 5: isEnabled= False
// 6: isEnabled= False
// 7: isEnabled= False
// 8: isEnabled= False
// 9: isEnabled= False
// 0: isEnabled= False
// Application ending.