Oyun geliştirmedeki bazı görevler eşzamanlı değildir; eşzamansızdırlar. Bu, oyun kodu içerisinde doğrusal olarak yürütülmedikleri anlamına gelir. Bu asenkron görevlerden bazılarının tamamlanması oldukça uzun zaman alabilir, bazıları ise yoğun hesaplamalarla ilişkilidir. En yaygın eşzamansız oyun görevlerinden bazıları şunlardır: Ağ isteklerini gerçekleştirme Sahneleri, kaynakları ve diğer varlıkları yükleme Dosyaları okuma ve yazma Karar vermede yapay zeka Uzun animasyon dizileri Büyük miktarda verinin işlenmesi Yol bulma Şimdi, en önemlisi, tüm Unity kodu tek bir iş parçacığında çalıştığından, yukarıda bahsedilenlere benzer herhangi bir görev, eşzamanlı olarak gerçekleştirilirse ana iş parçacığının engellenmesine ve dolayısıyla çerçevenin düşmesine yol açacaktır. Herkese merhaba, adım Dmitrii Ivashchenko ve MY.GAMES'in Geliştirme Ekibinin Başkanıyım. Bu yazımızda bu tür sorunlardan kaçınmanın yollarını konuşacağız. Bu görevleri ayrı bir iş parçacığında gerçekleştirmek için eşzamansız programlama teknikleri önereceğiz, böylece ana iş parçacığını diğer görevleri gerçekleştirmek için serbest bırakacağız. Bu, sorunsuz ve duyarlı bir oyun deneyiminin ve (umarız) oyuncuların memnun kalmasının sağlanmasına yardımcı olacaktır. Eşyordamlar Öncelikle Bunlar, .NET'te async / wait ortaya çıkmadan önce bile 2011 yılında Unity'de tanıtıldı. Unity'de eşyordamlar, bir dizi talimatı tek seferde yürütmek yerine birden çok çerçeve üzerinde yürütmemize olanak tanır. Konulara benzerler ancak hafiftirler ve Unity'nin güncelleme döngüsüne entegre edilmişlerdir, bu da onları oyun geliştirme için çok uygun kılar. koroutinler hakkında konuşalım. (Bu arada, tarihsel olarak konuşursak, eşyordamlar Unity'de eşzamansız işlemleri gerçekleştirmenin ilk yoluydu, bu nedenle İnternet'teki çoğu makale onlar hakkındadır.) Bir eşyordam oluşturmak için dönüş türüne sahip bir işlev bildirmeniz gerekir. Bu fonksiyon, eşyordamın yürütmesini istediğiniz herhangi bir mantığı içerebilir. IEnumerator Bir eşyordam başlatmak için örneğinde yöntemini çağırmanız ve eşyordam işlevini argüman olarak iletmeniz gerekir: MonoBehaviour StartCoroutine public class Example : MonoBehaviour { void Start() { StartCoroutine(MyCoroutine()); } IEnumerator MyCoroutine() { Debug.Log("Starting coroutine"); yield return null; Debug.Log("Executing coroutine"); yield return null; Debug.Log("Finishing coroutine"); } } Unity'de , , , , ve diğerleri gibi çeşitli verim talimatları vardır. Bunları kullanmanın tahsislere yol açtığını unutmamak önemlidir, dolayısıyla mümkün olan her yerde yeniden kullanılmaları gerekir. WaitForSeconds WaitForEndOfFrame WaitForFixedUpdate WaitForSecondsRealtime WaitUntil Örneğin, bu yöntemi belgelerden düşünün: IEnumerator Fade() { Color c = renderer.material.color; for (float alpha = 1f; alpha >= 0; alpha -= 0.1f) { ca = alpha; renderer.material.color = c; yield return new WaitForSeconds(.1f); } } Döngünün her yinelemesinde yeni bir örneği oluşturulacaktır. Bunun yerine, yaratımı döngünün dışına taşıyabilir ve tahsislerden kaçınabiliriz: new WaitForSeconds(.1f) IEnumerator Fade() { Color c = renderer.material.color; **var waitForSeconds = new WaitForSeconds(0.2f);** for (float alpha = 1f; alpha >= 0; alpha -= 0.1f) { ca = alpha; renderer.material.color = c; yield return **waitForSeconds**; } } Dikkat edilmesi gereken bir diğer önemli özellik, s'nin soyundan gelmesi nedeniyle Unity tarafından sağlanan tüm yöntemleriyle kullanılabilmesidir: AsyncOperation YieldInstruction yield return Async yield return SceneManager.LoadSceneAsync("path/to/scene.unity"); Eşyordamların bazı olası tuzakları Bütün bunlar söyleniyor, eşyordamların ayrıca dikkat edilmesi gereken birkaç dezavantajı var: Uzun bir operasyonun sonucunu döndürmek imkansızdır. Yine de eşyordama iletilecek ve ondan herhangi bir veri çıkarmak için bittiğinde çağrılacak geri aramalara ihtiyacınız var. Bir eşyordam, onu başlatan sıkı sıkıya bağlıdır. kapatılırsa veya yok edilirse eşyordamın işlenmesi durdurulur. MonoBehaviour GameObject Getiri sözdiziminin varlığı nedeniyle yapısı kullanılamaz. try-catch-finally Bir sonraki kodun yürütülmesine başlamadan önce sonra en az bir kare geçecektir. yield return Lambdanın ve eşyordamın kendisinin tahsisi Vaatler eşzamansız işlemleri düzenlemek ve daha okunaklı hale getirmek için kullanılan bir kalıptır. Birçok üçüncü taraf JavaScript kitaplığında kullanılmaları nedeniyle popüler hale geldiler ve ES6'dan bu yana yerel olarak uygulandılar. Vaatler, Promises'ı kullanırken, eşzamansız işlevinizden hemen bir nesne döndürürüz. Bu, arayanın işlemin çözümünü (veya bir hatasını) beklemesine olanak tanır. Temel olarak bu, eşzamansız yöntemlerin değerleri döndürebilmesini ve eşzamanlı yöntemler gibi "hareket edebilmesini" sağlar: nihai değeri hemen döndürmek yerine, gelecekte bir zamanda bir değer döndüreceklerine dair bir "söz" verirler. Unity için çeşitli Promises uygulamaları vardır: C-Sharp-Söz UnityFx.Async C# Vaat Ediyor söz ver Bir Promise ile etkileşim kurmanın ana yolu . geri çağırma işlevleridir Bir Söz çözümlendiğinde çağrılacak bir geri çağırma işlevi ve Söz reddedilirse çağrılacak başka bir geri çağırma işlevi tanımlayabilirsiniz. Bu geri aramalar, eşzamansız işlemin sonucunu bağımsız değişkenler olarak alır ve bunlar daha sonra başka işlemler gerçekleştirmek için kullanılabilir. Promises/A+ bu göre bir Promise, üç durumdan birinde olabilir: organizasyonunun spesifikasyonlarına : başlangıç durumu, eşzamansız işlemin hala devam ettiği ve işlemin sonucunun henüz bilinmediği anlamına gelir. Pending ( ): çözümlenen duruma, işlemin sonucunu temsil eden bir değer eşlik eder. Fulfilled Resolved : Eşzamansız işlem herhangi bir nedenle başarısız olursa, Sözün "reddedildiği" söylenir. Reddedilen duruma başarısızlığın nedeni eşlik eder. Rejected Promises hakkında daha fazla bilgi Ek olarak sözler birbirine zincirlenebilir, böylece bir Sözün sonucu başka bir Sözün sonucunu belirlemek için kullanılabilir. Örneğin, bir sunucudan bazı verileri getiren bir Promise oluşturabilir ve ardından bu verileri, bazı hesaplamaları ve diğer eylemleri gerçekleştiren başka bir Promise oluşturmak için kullanabilirsiniz: var promise = MakeRequest("https://some.api") .Then(response => Parse(response)) .Then(result => OnRequestSuccess(result)) .Then(() => PlaySomeAnimation()) .Catch(exception => OnRequestFailed(exception)); Eşzamansız bir işlem gerçekleştiren bir yöntemin nasıl organize edileceğine dair bir örnek: public IPromise<string> MakeRequest(string url) { // Create a new promise object var promise = new Promise<string>(); // Create a new web client using var client = new WebClient(); // Add a handler for the DownloadStringCompleted event client.DownloadStringCompleted += (sender, eventArgs) => { // If an error occurred, reject the promise if (eventArgs.Error != null) { promise.Reject(eventArgs.Error); } // Otherwise, resolve the promise with the result else { promise.Resolve(eventArgs.Result); } }; // Start the download asynchronously client.DownloadStringAsync(new Uri(url), null); // Return the promise return promise; } Ayrıca eşyordamları bir içine sarabiliriz: Promise void Start() { // Load the scene and then show the intro animation LoadScene("path/to/scene.unity") .Then(() => ShowIntroAnimation()) .Then( ... ); } // Load a scene and return a promise Promise LoadScene(string sceneName) { // Create a new promise var promise = new Promise(); // Start a coroutine to load the scene StartCoroutine(LoadSceneRoutine(promise, sceneName)); // Return the promise return promise; } IEnumerator LoadSceneRoutine(Promise promise, string sceneName) { // Load the scene asynchronously yield return SceneManager.LoadSceneAsync(sceneName); // Resolve the promise once the scene is loaded promise.Resolve(); } Ve elbette, / ve / kullanarak herhangi bir söz yürütme sırası kombinasyonunu düzenleyebilirsiniz: ThenAll Promise.All ThenRace Promise.Race // Execute the following two promises in sequence Promise.Sequence( () => Promise.All( // Execute the two promises in parallel RunAnimation("Foo"), PlaySound("Bar") ), () => Promise.Race( // Execute the two promises in a race RunAnimation("One"), PlaySound("Two") ) ); Sözlerin “taviz vermeyen” kısımları Tüm kullanım kolaylığına rağmen vaatlerin bazı dezavantajları da vardır: : Söz Oluşturmak, eşyordamlar gibi diğer eşzamansız programlama yöntemlerinin kullanılmasıyla karşılaştırıldığında ek yük gerektirir. Bazı durumlarda bu, performansın düşmesine neden olabilir. Ek Yük : Promises'ta hata ayıklama, diğer eşzamansız programlama modellerinde hata ayıklamaktan daha zor olabilir. Yürütme akışını izlemek ve hataların kaynağını belirlemek zor olabilir. Hata Ayıklama : İstisna işleme, diğer eşzamansız programlama modelleriyle karşılaştırıldığında Promises ile daha karmaşık olabilir. Promise zincirinde meydana gelen hataları ve istisnaları yönetmek zor olabilir. İstisna İşleme Eşzamansız/Bekleme Görevleri Eşzamansız/beklemede özelliği, sürüm 5.0'dan (2012) beri C#'ın bir parçası olmuştur ve Unity 2017'de .NET 4.x çalışma zamanının uygulanmasıyla tanıtılmıştır. .NET'in tarihinde aşağıdaki aşamalar ayırt edilebilir: (Olay Tabanlı Asenkron Model): Bu yaklaşım, bir işlemin tamamlanmasıyla tetiklenen olaylara ve bu işlemi çağıran düzenli bir yönteme dayanmaktadır. EAP (Asynchronous Programming Model): Bu yaklaşım iki yönteme dayanmaktadır. yöntemi arayüzünü döndürür. yöntemi değerini alır; çağrısı sırasında işlem tamamlanmazsa iş parçacığı engellenir. APM BeginSmth IAsyncResult EndSmth IAsyncResult EndSmth (Görev Tabanlı Eşzamansız Desen): Bu kavram, eşzamansız/beklemede ve ve türlerinin tanıtılmasıyla geliştirildi. TAP Task Task<TResult> Son yaklaşımın başarısı nedeniyle önceki yaklaşımlar geçerliliğini yitirdi. Eşzamansız bir yöntem oluşturmak için yöntemin anahtar sözcüğüyle işaretlenmesi, içinde bir içermesi ve dönüş değerinin , veya (önerilmez) olması gerekir. async await Task Task<T> void public async Task Example() { SyncMethodA(); await Task.Delay(1000); // the first async operation SyncMethodB(); await Task.Delay(2000); // the second async operation SyncMethodC(); await Task.Delay(3000); // the third async operation } Bu örnekte yürütme şu şekilde gerçekleşecektir: İlk olarak, ilk eşzamansız işleme ( ) yapılan çağrıdan önceki kod yürütülecektir. SyncMethodA İlk eşzamansız işlem başlatıldı ve yürütülmesi bekleniyor. Bu arada asenkron işlem tamamlandığında çağrılacak kod ("devamı") kaydedilecektir. await Task.Delay(1000) İlk eşzamansız işlem tamamlandıktan sonra, "devam" — sonraki eşzamansız işleme ( ) kadar olan kod yürütülmeye başlayacaktır. SyncMethodB İkinci eşzamansız işlem ( ) başlatılır ve yürütülmesi beklenir. Aynı zamanda, ikinci eşzamansız işlemi ( ) takip eden kod olan devamı da korunacaktır. await Task.Delay(2000) SyncMethodC İkinci eşzamansız işlemin tamamlanmasından sonra yürütülecek, ardından yürütülecek ve üçüncü eşzamansız işlemin beklenmesi . SyncMethodC await Task.Delay(3000) Bu basitleştirilmiş bir açıklamadır, çünkü asenkron/bekleme, asenkron yöntemlerin uygun şekilde çağrılmasına ve bunların tamamlanmasının beklenmesine izin veren sözdizimsel şekerdir. Ayrıca ve kullanarak herhangi bir yürütme emri kombinasyonunu da düzenleyebilirsiniz: WhenAll WhenAny var allTasks = Task.WhenAll( Task.Run(() => { /* ... */ }), Task.Run(() => { /* ... */ }), Task.Run(() => { /* ... */ }) ); allTasks.ContinueWith(t => { Console.WriteLine("All the tasks are completed"); }); var anyTask = Task.WhenAny( Task.Run(() => { /* ... */ }), Task.Run(() => { /* ... */ }), Task.Run(() => { /* ... */ }) ); anyTask.ContinueWith(t => { Console.WriteLine("One of tasks is completed"); }); IAsyncStateMachine derleyicisi, eşzamansız/beklemede çağrıları, eşzamansız işlemi tamamlamak için gerçekleştirilmesi gereken sıralı bir dizi eylemden oluşan bir durum makinesine dönüştürür. C# IAsyncStateMachine Bir bekleme işlemini her çağırdığınızda, durum makinesi işini tamamlar ve o işlemin tamamlanmasını bekler, ardından bir sonraki işlemi yürütmeye devam eder. Bu, asenkron işlemlerin ana iş parçacığını engellemeden arka planda gerçekleştirilmesine olanak tanır ve aynı zamanda asenkron yöntem çağrılarını daha basit ve daha okunaklı hale getirir. Böylece, yöntemi ek açıklamasıyla bir durum makinesi oluşturmaya ve başlatmaya dönüştürülür ve durum makinesinin kendisi, bekleme çağrılarının sayısına eşit sayıda duruma sahiptir. Example [AsyncStateMachine(typeof(ExampleStateMachine))] Dönüştürülen yöntemin Örnek Example [AsyncStateMachine(typeof(ExampleStateMachine))] public /*async*/ Task Example() { // Create a new instance of the ExampleStateMachine class ExampleStateMachine stateMachine = new ExampleStateMachine(); // Create a new AsyncTaskMethodBuilder and assign it to the taskMethodBuilder property of the stateMachine instance stateMachine.taskMethodBuilder = AsyncTaskMethodBuilder.Create(); // Set the currentState property of the stateMachine instance to -1 stateMachine.currentState = -1; // Start the stateMachine instance stateMachine.taskMethodBuilder.Start(ref stateMachine); // Return the Task property of the taskMethodBuilder return stateMachine.taskMethodBuilder.Task; } Oluşturulan bir durum makinesi örneği ExampleStateMachine [CompilerGenerated] private sealed class ExampleStateMachine : IAsyncStateMachine { public int currentState; public AsyncTaskMethodBuilder taskMethodBuilder; private TaskAwaiter taskAwaiter; public int paramInt; private int localInt; void IAsyncStateMachine.MoveNext() { int num = currentState; try { TaskAwaiter awaiter3; TaskAwaiter awaiter2; TaskAwaiter awaiter; switch (num) { default: localInt = paramInt; // Call the first synchronous method SyncMethodA(); // Create a task awaiter for a delay of 1000 milliseconds awaiter3 = Task.Delay(1000).GetAwaiter(); // If the task is not completed, set the current state to 0 and store the awaiter if (!awaiter3.IsCompleted) { currentState = 0; taskAwaiter = awaiter3; // Store the current state machine ExampleStateMachine stateMachine = this; // Await the task and pass the state machine taskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine); return; } // If the task is completed, jump to the label after the first await goto Il_AfterFirstAwait; case 0: // Retrieve the awaiter from the taskAwaiter field awaiter3 = taskAwaiter; // Reset the taskAwaiter field taskAwaiter = default(TaskAwaiter); currentState = -1; // Jump to the label after the first await goto Il_AfterFirstAwait; case 1: // Retrieve the awaiter from the taskAwaiter field awaiter2 = taskAwaiter; // Reset the taskAwaiter field taskAwaiter = default(TaskAwaiter); currentState = -1; // Jump to the label after the second await goto Il_AfterSecondAwait; case 2: // Retrieve the awaiter from the taskAwaiter field awaiter = taskAwaiter; // Reset the taskAwaiter field taskAwaiter = default(TaskAwaiter); currentState = -1; break; Il_AfterFirstAwait: awaiter3.GetResult(); // Call the second synchronous method SyncMethodB(); // Create a task awaiter for a delay of 2000 milliseconds awaiter2 = Task.Delay(2000).GetAwaiter(); // If the task is not completed, set the current state to 1 and store the awaiter if (!awaiter2.IsCompleted) { currentState = 1; taskAwaiter = awaiter2; // Store the current state machine ExampleStateMachine stateMachine = this; // Await the task and pass the state machine taskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine); return; } // If the task is completed, jump to the label after the second await goto Il_AfterSecondAwait; Il_AfterSecondAwait: // Get the result of the second awaiter awaiter2.GetResult(); // Call the SyncMethodC SyncMethodC(); // Create a new awaiter with a delay of 3000 milliseconds awaiter = Task.Delay(3000).GetAwaiter(); // If the awaiter is not completed, set the current state to 2 and store the awaiter if (!awaiter.IsCompleted) { currentState = 2; taskAwaiter = awaiter; // Set the stateMachine to this ExampleStateMachine stateMachine = this; // Await the task and pass the state machine taskMethodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } break; } // Get the result of the awaiter awaiter.GetResult(); } catch (Exception exception) { currentState = -2; taskMethodBuilder.SetException(exception); return; } currentState = -2; taskMethodBuilder.SetResult(); } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { /*...*/ } } Senkronizasyon Bağlamı çağrısında geçerli senkronizasyon bağlamı elde edilecektir. , C#'ta bir dizi eşzamansız işlemin yürütülmesini kontrol eden bir bağlamı temsil etmek için kullanılan bir kavramdır. Kodun birden fazla iş parçacığı boyunca yürütülmesini koordine etmek ve kodun belirli bir sırayla yürütülmesini sağlamak için kullanılır. SynchronizationContext'in temel amacı, çok iş parçacıklı bir ortamda eşzamansız işlemlerin zamanlanmasını ve yürütülmesini denetlemenin bir yolunu sağlamaktır. AwaitUnsafeOnCompleted SynchronizationContext SynchronizationContext Farklı ortamlarda farklı uygulamaları vardır. Örneğin, .NET'te şunlar vardır: SynchronizationContext : WPF System.Windows.Threading.DispatcherSynchronizationContext : WinForms System.Windows.Forms.WindowsFormsSynchronizationContext : WinRT System.Threading.WinRTSynchronizationContext : ASP.NET System.Web.AspNetSynchronizationContext ayrıca PlayerLoop API'sine bağlanma ile eşzamansız işlemleri kullanmamızı sağlayan kendi senkronizasyon bağlamı vardır. Aşağıdaki kod örneği, kullanılarak her karede bir nesnenin nasıl döndürüleceğini gösterir: Unity'nin UnitySynchronizationContext Task.Yield() private async void Start() { while (true) { transform.Rotate(0, Time.deltaTime * 50, 0); await Task.Yield(); } } Bir ağ isteğinde bulunmak için Unity'de eşzamansız/beklemede kullanmanın başka bir örneği: using UnityEngine; using System.Net.Http; using System.Threading.Tasks; public class NetworkRequestExample : MonoBehaviour { private async void Start() { string response = await GetDataFromAPI(); Debug.Log("Response from API: " + response); } private async Task<string> GetDataFromAPI() { using (var client = new HttpClient()) { var response = await client.GetStringAsync("https://api.example.com/data"); return response; } } } sayesinde, bu kodun yürütülmesi ana Unity iş parçacığında devam edeceğinden, asenkron bir işlem tamamlandıktan hemen sonra yöntemlerini ( gibi) güvenle kullanabiliriz. UnitySynchronizationContext UnityEngine Debug.Log() Görev Tamamlama Kaynağı<T> Bu sınıf bir nesnesini yönetmenize olanak tanır. Eski eşzamansız yöntemleri TAP'a uyarlamak için oluşturuldu, ancak aynı zamanda bir bazı olaylar üzerine uzun süren bir işlemin etrafına sarmak istediğimizde de çok kullanışlıdır. Task Task Aşağıdaki örnekte, içindeki nesnesi başlangıçtan itibaren 3 saniye sonra tamamlanacak ve sonucunu yönteminde alacağız: taskCompletionSource Task Update using System.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private TaskCompletionSource<int> taskCompletionSource; private void Start() { // Create a new TaskCompletionSource taskCompletionSource = new TaskCompletionSource<int>(); // Start a coroutine to wait 3 seconds // and then set the result of the TaskCompletionSource StartCoroutine(WaitAndComplete()); } private IEnumerator WaitAndComplete() { yield return new WaitForSeconds(3); // Set the result of the TaskCompletionSource taskCompletionSource.SetResult(10); } private async void Update() { // Await the result of the TaskCompletionSource int result = await taskCompletionSource.Task; // Log the result to the console Debug.Log("Result: " + result); } } İptal Jetonu C#'ta bir görevin veya işlemin iptal edilmesi gerektiğini bildirmek için kullanılır. Belirteç göreve veya işleme iletilir ve görev veya işlem içindeki kod, görevin veya işlemin durdurulması gerekip gerekmediğini belirlemek için belirteci periyodik olarak kontrol edebilir. Bu, bir görevin veya işlemin aniden sonlandırılması yerine temiz ve zarif bir şekilde iptal edilmesine olanak tanır. İptal Tokenı İptal Jetonları, uzun süredir devam eden bir görevin kullanıcı tarafından iptal edilebildiği veya kullanıcı arayüzündeki iptal düğmesi gibi göreve artık ihtiyaç duyulmadığı durumlarda yaygın olarak kullanılır. Genel desen kullanımına benzer. İlk önce bir oluşturulur, ardından eşzamansız işleme aktarılır: TaskCompletionSource CancellationTokenSource Token public class ExampleMonoBehaviour : MonoBehaviour { private CancellationTokenSource _cancellationTokenSource; private async void Start() { // Create a new CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); // Get the token from the CancellationTokenSource CancellationToken token = _cancellationTokenSource.Token; try { // Start a new Task and pass in the token await Task.Run(() => DoSomething(token), token); } catch (OperationCanceledException) { Debug.Log("Task was cancelled"); } } private void DoSomething(CancellationToken token) { for (int i = 0; i < 100; i++) { // Check if the token has been cancelled if (token.IsCancellationRequested) { // Return if the token has been cancelled return; } Debug.Log("Doing something..."); // Sleep for 1 second Thread.Sleep(1000); } } private void OnDestroy() { // Cancel the token when the object is destroyed _cancellationTokenSource.Cancel(); } } İşlem iptal edildiğinde, bir oluşturulacak ve özelliği olarak ayarlanacaktır. OperationCanceledException Task.IsCanceled true Unity 2022.2'deki yeni eşzamansız özellikler nesnelerinin Unity tarafından değil, .NET çalışma zamanı tarafından yönetildiğini ve görevi yürüten nesne yok edilirse (veya oyun düzenleyicide oynatma modundan çıkarsa), görevin Unity'nin yaptığı gibi çalışmaya devam edeceğini unutmamak önemlidir. iptal etmenin hiçbir yolu yok. Task Her zaman karşılık gelen ile eşlik etmeniz gerekir. Bu, bir miktar kod fazlalığına yol açar ve Unity 2022.2'de düzeyinde ve tüm düzeyinde yerleşik belirteçler ortaya çıktı. await Task CancellationToken MonoBehaviour Application nesnesinin kullanıldığında önceki örneğin nasıl değiştiğini görelim: MonoBehaviour destroyCancellationToken using System.Threading; using System.Threading.Tasks; using UnityEngine; public class ExampleMonoBehaviour : MonoBehaviour { private async void Start() { // Get the cancellation token from the MonoBehaviour CancellationToken token = this.destroyCancellationToken; try { // Start a new Task and pass in the token await Task.Run(() => DoSomething(token), token); } catch (OperationCanceledException) { Debug.Log("Task was cancelled"); } } private void DoSomething(CancellationToken token) { for (int i = 0; i < 100; i++) { // Check if the token has been cancelled if (token.IsCancellationRequested) { // Return if the token has been cancelled return; } Debug.Log("Doing something..."); // Sleep for 1 second Thread.Sleep(1000); } } } Artık manuel olarak oluşturmamıza ve görevi yönteminde tamamlamamıza gerek yok. Belirli bir ile ilişkili olmayan görevler için kullanabiliriz. Bu, Oynatma Modundan (Editörde) çıkarken veya uygulamadan çıkarken görevi sonlandıracaktır. CancellationTokenSource OnDestroy MonoBehaviour UnityEngine.Application.exitCancellationToken Tek Görev Kullanım kolaylığına ve .NET Görevleri'nin sağladığı yeteneklere rağmen, Unity'de kullanıldıklarında önemli dezavantajlara sahiptirler: nesneleri çok hantaldır ve birçok tahsise neden olur. Task Unity iş parçacığı (tek iş parçacığı) ile eşleştirilmiyor. Task kitaplığı, iş parçacığı veya kullanmadan bu kısıtlamaları atlar. yapı tabanlı türünü kullanarak tahsislerin yokluğunu başarır. UniTask SynchronizationContext UniTask<T> UniTask, .NET 4.x komut dosyası oluşturma çalışma zamanı sürümünü gerektirir; Unity 2018.4.13f1, desteklenen en düşük resmi sürümdür. Ayrıca tüm uzantı yöntemleriyle dönüştürebilirsiniz: AsyncOperations UnitTask using UnityEngine; using UniTask; public class AssetLoader : MonoBehaviour { public async void LoadAsset(string assetName) { var loadRequest = Resources.LoadAsync<GameObject>(assetName); await loadRequest.AsUniTask(); var asset = loadRequest.asset as GameObject; if (asset != null) { // Do something with the loaded asset } } } Bu örnekte yöntemi, bir varlığı eşzamansız olarak yüklemek için kullanır. yöntemi daha sonra tarafından döndürülen beklenebilecek bir dönüştürmek için kullanılır. LoadAsset Resources.LoadAsync AsUniTask LoadAsync AsyncOperation UniTask Daha önce olduğu gibi, ve kullanarak herhangi bir yürütme sırası kombinasyonunu düzenleyebilirsiniz: UniTask.WhenAll UniTask.WhenAny using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine; public class Example : MonoBehaviour { private async void Start() { // Start two Tasks and wait for both to complete await UniTask.WhenAll(Task1(), Task2()); // Start two Tasks and wait for one to complete await UniTask.WhenAny(Task1(), Task2()); } private async UniTask Task1() { // Do something } private async UniTask Task2() { // Do something } } UniTask'ta, daha iyi performans için yerine kullanılabilecek adı verilen başka bir uygulaması vardır. UnitySynchronizationContext UniTaskSynchronizationContext SynchronizationContext Beklenen API Unity 2023.1'in ilk alfa sürümünde sınıfı tanıtıldı. Awaitable Coroutine'ler, Unity'de çalışmak üzere tasarlanmış, eşzamansız/beklemede uyumlu Görev benzeri türlerdir. .NET Görevlerinden farklı olarak, çalışma zamanı tarafından değil motor tarafından yönetilirler. Awaitable private async Awaitable DoSomethingAsync() { // awaiting built-in events await Awaitable.EndOfFrameAsync(); await Awaitable.WaitForSecondsAsync(); // awaiting .NET Tasks await Task.Delay(2000, destroyCancellationToken); await Task.Yield(); // awaiting AsyncOperations await SceneManager.LoadSceneAsync("path/to/scene.unity"); // ... } Beklenebilir ve zaman uyumsuz bir yöntemin dönüş türü olarak kullanılabilirler. ile karşılaştırıldığında daha az karmaşıktırlar ancak Unity'ye özgü varsayımlara dayalı performans artırıcı kısayollar kullanırlar. System.Threading.Tasks .NET Görevleri ile karşılaştırıldığında temel farklar şunlardır: nesnesi yalnızca bir kez beklenebilir; birden fazla eşzamansız işlev tarafından beklenemez. Awaitable tamamlanana kadar engellemez. İşlem bitmeden onu çağırmak tanımsız bir davranıştır. Awaiter.GetResults() Asla bir yakalamayın. Güvenlik nedeniyle, .NET Görevleri, kimliğe bürünme bağlamlarını eşzamansız çağrılar arasında yaymak için bekleme sırasında yürütme bağlamlarını yakalar. ExecutionContext Hiçbir zaman yakalamayın. Eşyordam devamları, tamamlamayı artıran koddan eşzamanlı olarak yürütülür. Çoğu durumda bu, Unity ana çerçevesinden olacaktır. SynchronizationContext Beklentiler, aşırı tahsisleri önlemek için havuzlanmış nesnelerdir. Bunlar referans türleridir, dolayısıyla farklı yığınlarda referans verilebilir, verimli bir şekilde kopyalanabilir vb. eşzamansız durum makineleri tarafından oluşturulan tipik alma/bırakma dizilerindeki sınır denetimlerini önleyecek şekilde geliştirildi. ObjectPool Stack<T> Uzun bir işlemin sonucunu elde etmek için tipini kullanabilirsiniz. ve kullanarak benzer şekilde bir tamamlanmasını yönetebilirsiniz: Awaitable<T> AwaitableCompletionSource AwaitableCompletionSource<T> , TaskCompletitionSource Awaitable using UnityEngine; using Cysharp.Threading.Tasks; public class ExampleBehaviour : MonoBehaviour { private AwaitableCompletionSource<bool> _completionSource; private async void Start() { // Create a new AwaitableCompletionSource _completionSource = new AwaitableCompletionSource<bool>(); // Start a coroutine to wait 3 seconds // and then set the result of the AwaitableCompletionSource StartCoroutine(WaitAndComplete()); // Await the result of the AwaitableCompletionSource bool result = await _completionSource.Awaitable; // Log the result to the console Debug.Log("Result: " + result); } private IEnumerator WaitAndComplete() { yield return new WaitForSeconds(3); // Set the result of the AwaitableCompletionSource _completionSource.SetResult(true); } } Bazen oyunun donmasına yol açabilecek devasa hesaplamalar yapmak gerekebilir. Bunun için Awaitable yöntemlerini kullanmak daha iyidir: ve . Ana başlıktan çıkıp ona geri dönmenizi sağlarlar. BackgroundThreadAsync() MainThreadAsync() private async Awaitable DoCalculationsAsync() { // Awaiting execution on a ThreadPool background thread. await Awaitable.BackgroundThreadAsync(); var result = PerformSomeHeavyCalculations(); // Awaiting execution on the Unity main thread. await Awaitable.MainThreadAsync(); // Using the result in main thread Debug.Log(result); } Bu şekilde Awaitables, .NET Görevlerini kullanmanın dezavantajlarını ortadan kaldırır ve ayrıca PlayerLoop olaylarının ve AsyncOperations'ın beklenmesine olanak tanır. Çözüm Gördüğümüz gibi Unity'nin gelişmesiyle birlikte asenkron işlemleri organize etmek için giderek daha fazla araç ortaya çıktı: Birlik Eşyordamlar Vaatler .NET Görevleri Tek Görev Yerleşik İptal Jetonları Beklenen API 5.6 ✅ ✅ 2017.1 ✅ ✅ ✅ 2018.4 ✅ ✅ ✅ ✅ 2022.2 ✅ ✅ ✅ ✅ ✅ 2023.1 ✅ ✅ ✅ ✅ ✅ ✅ Unity'de asenkron programlamanın tüm ana yollarını düşündük. Görevinizin karmaşıklığına ve kullandığınız Unity sürümüne bağlı olarak, oyunlarınızda sorunsuz ve kesintisiz bir oyun deneyimi sağlamak için Coroutines ve Promises'tan Görevler ve Awaitables'a kadar çok çeşitli teknolojileri kullanabilirsiniz. Okuduğunuz için teşekkürler, bir sonraki şaheserlerinizi bekliyoruz.