paint-brush
ইউনিটি ডেভেলপমেন্টের জন্য সম্পূর্ণ অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং প্রাইমারদ্বারা@dmitrii
5,692 পড়া
5,692 পড়া

ইউনিটি ডেভেলপমেন্টের জন্য সম্পূর্ণ অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং প্রাইমার

দ্বারা Dmitrii Ivashchenko31m2023/03/30
Read on Terminal Reader
Read this story w/o Javascript

অতিদীর্ঘ; পড়তে

এই নিবন্ধে, আমরা এই ধরনের সমস্যা এড়ানোর বিষয়ে কথা বলতে যাচ্ছি। আমরা একটি পৃথক থ্রেডে এই কাজগুলি সম্পাদন করার জন্য অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং কৌশলগুলি সুপারিশ করব, এইভাবে অন্যান্য কাজগুলি সম্পাদন করার জন্য মূল থ্রেডটি বিনামূল্যে রেখে দেওয়া হবে। এটি মসৃণ এবং প্রতিক্রিয়াশীল গেমপ্লে এবং (আশা করি) সন্তুষ্ট গেমারদের নিশ্চিত করতে সহায়তা করবে।
featured image - ইউনিটি ডেভেলপমেন্টের জন্য সম্পূর্ণ অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং প্রাইমার
Dmitrii Ivashchenko HackerNoon profile picture
0-item

গেম ডেভেলপমেন্টের কিছু কাজ সিঙ্ক্রোনাস নয় - সেগুলি অ্যাসিঙ্ক্রোনাস। এর মানে এগুলি গেম কোডের মধ্যে রৈখিকভাবে কার্যকর করা হয় না। এই অসিঙ্ক্রোনাস কাজগুলির মধ্যে কিছু সম্পূর্ণ করতে বেশ দীর্ঘ সময়ের প্রয়োজন হতে পারে, অন্যগুলি নিবিড় গণনার সাথে যুক্ত।


কিছু সাধারণ গেমিং অ্যাসিঙ্ক্রোনাস কাজগুলি নিম্নরূপ:

  • নেটওয়ার্ক অনুরোধ সঞ্চালন

  • দৃশ্য, সম্পদ, এবং অন্যান্য সম্পদ লোড হচ্ছে

  • ফাইল পড়া এবং লেখা

  • সিদ্ধান্ত নেওয়ার জন্য কৃত্রিম বুদ্ধিমত্তা

  • দীর্ঘ অ্যানিমেশন ক্রম

  • প্রচুর পরিমাণে ডেটা প্রক্রিয়াকরণ

  • রাস্তা খোঁজা


এখন, গুরুত্বপূর্ণভাবে, যেহেতু সমস্ত ইউনিটি কোড এক থ্রেডে চলে, উপরে উল্লিখিতগুলির মধ্যে একটির মতো যে কোনও কাজ, যদি সেগুলি সিঙ্ক্রোনাসভাবে সম্পাদিত হয়, তাহলে মূল থ্রেডটি ব্লক হয়ে যাবে, এবং এইভাবে, ফ্রেম ড্রপ হবে।


সবাইকে হ্যালো, আমার নাম দিমিত্রি ইভাশচেঙ্কো এবং আমি MY.GAMES-এ ডেভেলপমেন্ট টিমের প্রধান। এই নিবন্ধে, আমরা এই ধরনের সমস্যা এড়ানোর বিষয়ে কথা বলতে যাচ্ছি। আমরা একটি পৃথক থ্রেডে এই কাজগুলি সম্পাদন করার জন্য অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং কৌশলগুলি সুপারিশ করব, এইভাবে অন্যান্য কাজগুলি সম্পাদনের জন্য মূল থ্রেডটি মুক্ত থাকবে। এটি মসৃণ এবং প্রতিক্রিয়াশীল গেমপ্লে এবং (আশা করি) সন্তুষ্ট গেমারদের নিশ্চিত করতে সহায়তা করবে।

করুটিন

প্রথমত, আসুন কোরোটিন সম্পর্কে কথা বলি। তারা 2011 সালে ইউনিটিতে চালু হয়েছিল, এমনকি .NET-তে async/await উপস্থিত হওয়ার আগেই। ইউনিটিতে, কোরোটিনগুলি আমাদেরকে একাধিক ফ্রেমে নির্দেশাবলীর একটি সেট সম্পাদন করার অনুমতি দেয়, সেগুলি একবারে কার্যকর করার পরিবর্তে। এগুলি থ্রেডের মতো, তবে হালকা ওজনের এবং ইউনিটির আপডেট লুপের সাথে একত্রিত, গেম ডেভেলপমেন্টের জন্য তাদের উপযুক্ত করে তোলে।


(যাইহোক, ঐতিহাসিকভাবে বলতে গেলে, Coroutines ছিল ইউনিটিতে অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপ সম্পাদনের প্রথম উপায়, তাই ইন্টারনেটে বেশিরভাগ নিবন্ধ তাদের সম্পর্কে।)


একটি coroutine তৈরি করতে, আপনাকে IEnumerator রিটার্ন টাইপ সহ একটি ফাংশন ঘোষণা করতে হবে। এই ফাংশনটিতে যেকোন যুক্তি থাকতে পারে যা আপনি করুটিনকে কার্যকর করতে চান।


একটি coroutine শুরু করতে, আপনাকে MonoBehaviour উদাহরণে StartCoroutine পদ্ধতিতে কল করতে হবে এবং একটি যুক্তি হিসাবে coroutine ফাংশনটি পাস করতে হবে:

 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"); } }


ইউনিটিতে বেশ কিছু ইল্ড নির্দেশাবলী উপলব্ধ রয়েছে, যেমন WaitForSeconds , WaitForEndOfFrame , WaitForFixedUpdate , WaitForSecondsRealtime , WaitUntil এবং কিছু অন্যান্য৷ এটা মনে রাখা গুরুত্বপূর্ণ যে সেগুলি ব্যবহার করলে বরাদ্দ হয়, তাই যেখানেই সম্ভব সেগুলি পুনরায় ব্যবহার করা উচিত৷


উদাহরণস্বরূপ, ডকুমেন্টেশন থেকে এই পদ্ধতিটি বিবেচনা করুন:

 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); } }


লুপের প্রতিটি পুনরাবৃত্তির সাথে, 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**; } }


লক্ষণীয় আরেকটি উল্লেখযোগ্য বৈশিষ্ট্য হল যে Unity দ্বারা প্রদত্ত সমস্ত Async পদ্ধতির সাথে yield return ব্যবহার করা যেতে পারে কারণ AsyncOperation গুলি YieldInstruction এর বংশধর:

 yield return SceneManager.LoadSceneAsync("path/to/scene.unity");

coroutines কিছু সম্ভাব্য ক্ষতি

এই সব বলা হচ্ছে, কোরোটিনগুলিরও লক্ষ করার মতো কয়েকটি ত্রুটি রয়েছে:


  • দীর্ঘ অপারেশনের ফলাফল ফিরিয়ে আনা অসম্ভব। আপনার এখনও কলব্যাক প্রয়োজন যা কোরোটিনে পাস করা হবে এবং এটি থেকে কোনো ডেটা বের করার জন্য এটি শেষ হলে কল করা হবে।
  • একটি coroutine কঠোরভাবে MonoBehaviour এর সাথে আবদ্ধ যা এটি চালু করে। GameObject বন্ধ বা ধ্বংস করা হলে, coroutine প্রক্রিয়া করা বন্ধ করে দেয়।
  • ফলন সিনট্যাক্সের উপস্থিতির কারণে try-catch-finally স্ট্রাকচার ব্যবহার করা যাবে না।
  • পরবর্তী কোড কার্যকর করা শুরু হওয়ার আগে yield return পরে অন্তত একটি ফ্রেম পাস হবে।
  • ল্যাম্বডা এবং কোরোটিন নিজেই বরাদ্দ

প্রতিশ্রুতি

প্রতিশ্রুতিগুলি অসিঙ্ক্রোনাস ক্রিয়াকলাপগুলিকে আরও পঠনযোগ্য করার জন্য একটি প্যাটার্ন। অনেক থার্ড-পার্টি জাভাস্ক্রিপ্ট লাইব্রেরিতে তাদের ব্যবহারের কারণে তারা জনপ্রিয় হয়ে উঠেছে এবং ES6 থেকে স্থানীয়ভাবে প্রয়োগ করা হয়েছে।


প্রতিশ্রুতি ব্যবহার করার সময়, আমরা অবিলম্বে আপনার অ্যাসিঙ্ক্রোনাস ফাংশন থেকে একটি বস্তু ফিরিয়ে দিই। এটি কলকারীকে অপারেশনের রেজোলিউশনের (বা একটি ত্রুটি) জন্য অপেক্ষা করতে দেয়।


মূলত, এটি এমন করে যাতে অ্যাসিঙ্ক্রোনাস পদ্ধতিগুলি মানগুলি ফেরত দিতে পারে এবং সিঙ্ক্রোনাস পদ্ধতির মতো "কাজ" করতে পারে: অবিলম্বে চূড়ান্ত মান ফেরত দেওয়ার পরিবর্তে, তারা একটি "প্রতিশ্রুতি" দেয় যে তারা ভবিষ্যতে কোনো একটি মান ফিরিয়ে দেবে।


ঐক্যের জন্য বেশ কয়েকটি প্রতিশ্রুতি বাস্তবায়ন রয়েছে:


একটি প্রতিশ্রুতির সাথে ইন্টারঅ্যাক্ট করার প্রধান উপায় হল কলব্যাক ফাংশনের মাধ্যমে।


আপনি একটি কলব্যাক ফাংশন সংজ্ঞায়িত করতে পারেন যা একটি প্রতিশ্রুতি সমাধান করা হলে কল করা হবে, এবং অন্য একটি কলব্যাক ফাংশন যা প্রতিশ্রুতি প্রত্যাখ্যান করা হলে কল করা হবে। এই কলব্যাকগুলি আর্গুমেন্ট হিসাবে অ্যাসিঙ্ক্রোনাস অপারেশনের ফলাফল গ্রহণ করে, যা পরবর্তী ক্রিয়াকলাপ সম্পাদন করতে ব্যবহার করা যেতে পারে।


তিনি প্রতিশ্রুতি/A+ সংস্থার এই স্পেসিফিকেশন অনুসারে, একটি প্রতিশ্রুতি তিনটি রাজ্যের একটিতে হতে পারে:


  • Pending : প্রাথমিক অবস্থা, এর মানে হল অ্যাসিঙ্ক্রোনাস অপারেশন এখনও চলছে, এবং অপারেশনের ফলাফল এখনও জানা যায়নি।
  • Fulfilled ( Resolved ): সমাধানকৃত অবস্থার সাথে একটি মান থাকে যা অপারেশনের ফলাফলকে প্রতিনিধিত্ব করে।
  • Rejected : অসিঙ্ক্রোনাস অপারেশন কোনো কারণে ব্যর্থ হলে, প্রতিশ্রুতি "প্রত্যাখ্যান" বলা হয়। প্রত্যাখ্যাত রাষ্ট্র ব্যর্থতার কারণ দ্বারা অনুষঙ্গী হয়.

প্রতিশ্রুতি আরো

উপরন্তু, প্রতিশ্রুতিগুলিকে একত্রে বেঁধে রাখা যেতে পারে, যাতে একটি প্রতিশ্রুতির ফলাফল অন্য প্রতিশ্রুতির ফলাফল নির্ধারণ করতে ব্যবহার করা যেতে পারে।


উদাহরণস্বরূপ, আপনি একটি প্রতিশ্রুতি তৈরি করতে পারেন যা একটি সার্ভার থেকে কিছু ডেটা নিয়ে আসে এবং তারপরে সেই ডেটা ব্যবহার করে অন্য একটি প্রতিশ্রুতি তৈরি করতে পারেন যা কিছু গণনা এবং অন্যান্য ক্রিয়া সম্পাদন করে:

 var promise = MakeRequest("https://some.api") .Then(response => Parse(response)) .Then(result => OnRequestSuccess(result)) .Then(() => PlaySomeAnimation()) .Catch(exception => OnRequestFailed(exception));


একটি অ্যাসিঙ্ক্রোনাস অপারেশন সম্পাদন করে এমন একটি পদ্ধতি কীভাবে সংগঠিত করা যায় তার একটি উদাহরণ এখানে রয়েছে:

 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; }


আমরা একটি 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(); }


এবং অবশ্যই, আপনি 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") ) );

প্রতিশ্রুতির "অপ্রত্যাশিত" অংশ

ব্যবহারের সমস্ত সুবিধা থাকা সত্ত্বেও, প্রতিশ্রুতিরও কিছু ত্রুটি রয়েছে:


  • ওভারহেড : প্রতিশ্রুতি তৈরি করার ক্ষেত্রে অসিঙ্ক্রোনাস প্রোগ্রামিং-এর অন্যান্য পদ্ধতি যেমন কোরোটিন ব্যবহার করার তুলনায় অতিরিক্ত ওভারহেড জড়িত। কিছু ক্ষেত্রে, এটি কর্মক্ষমতা হ্রাস হতে পারে।
  • ডিবাগিং : ডিবাগিং প্রতিশ্রুতি অন্যান্য অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং প্যাটার্নগুলি ডিবাগ করার চেয়ে আরও কঠিন হতে পারে। এক্সিকিউশন ফ্লো ট্রেস করা এবং বাগগুলির উত্স সনাক্ত করা কঠিন হতে পারে।
  • ব্যতিক্রম হ্যান্ডলিং : ব্যতিক্রম হ্যান্ডলিং অন্যান্য অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং প্যাটার্নের তুলনায় প্রতিশ্রুতির সাথে আরও জটিল হতে পারে। একটি প্রতিশ্রুতি শৃঙ্খলের মধ্যে ঘটে যাওয়া ত্রুটি এবং ব্যতিক্রমগুলি পরিচালনা করা কঠিন হতে পারে।

Async/অপেক্ষা করা টাস্ক

async/await বৈশিষ্ট্যটি সংস্করণ 5.0 (2012) থেকে C# এর একটি অংশ, এবং এটি .NET 4.x রানটাইম বাস্তবায়নের সাথে ইউনিটি 2017 এ চালু করা হয়েছিল।


.NET এর ইতিহাসে, নিম্নলিখিত পর্যায়গুলি আলাদা করা যেতে পারে:


  1. ইএপি (ইভেন্ট-ভিত্তিক অ্যাসিঙ্ক্রোনাস প্যাটার্ন): এই পদ্ধতিটি ইভেন্টগুলির উপর ভিত্তি করে তৈরি করা হয় যেগুলি একটি অপারেশন শেষ হওয়ার পরে ট্রিগার করা হয় এবং একটি নিয়মিত পদ্ধতি যা এই ক্রিয়াকলাপকে আহ্বান করে।
  2. এপিএম (অ্যাসিনক্রোনাস প্রোগ্রামিং মডেল): এই পদ্ধতিটি দুটি পদ্ধতির উপর ভিত্তি করে। BeginSmth পদ্ধতিটি IAsyncResult ইন্টারফেস প্রদান করে। EndSmth পদ্ধতিটি IAsyncResult নেয়; EndSmth কলের সময় অপারেশন সম্পূর্ণ না হলে, থ্রেডটি ব্লক করা হয়।
  3. TAP (টাস্ক-ভিত্তিক অ্যাসিঙ্ক্রোনাস প্যাটার্ন): এই ধারণাটি async/await এবং Task এবং Task<TResult> প্রবর্তনের মাধ্যমে উন্নত হয়েছে।


শেষ পদ্ধতির সাফল্যের কারণে পূর্ববর্তী পদ্ধতিগুলি অপ্রচলিত হয়েছিল।

একটি অ্যাসিঙ্ক্রোনাস পদ্ধতি তৈরি করতে, পদ্ধতিটিকে অবশ্যই 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 }


এই উদাহরণে, মৃত্যুদন্ড এভাবে সঞ্চালিত হবে:


  1. প্রথমত, প্রথম অ্যাসিঙ্ক্রোনাস অপারেশনে ( SyncMethodA ) কলের আগের কোডটি কার্যকর করা হবে।
  2. প্রথম অ্যাসিঙ্ক্রোনাস অপারেশন await Task.Delay(1000) চালু করা হয়েছে এবং এক্সিকিউট করা হবে বলে আশা করা হচ্ছে। এদিকে, অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপ সম্পূর্ণ হলে কল করা কোডটি ("চলতি") সংরক্ষণ করা হবে।
  3. প্রথম অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপ সম্পূর্ণ হওয়ার পরে, "চলমান" — পরবর্তী অ্যাসিঙ্ক্রোনাস অপারেশন ( SyncMethodB ) পর্যন্ত কোডটি কার্যকর করা শুরু করবে।
  4. দ্বিতীয় অ্যাসিঙ্ক্রোনাস অপারেশন ( await Task.Delay(2000) ) চালু হয়েছে এবং এটি কার্যকর হবে বলে আশা করা হচ্ছে। একই সময়ে, ধারাবাহিকতা — দ্বিতীয় অ্যাসিঙ্ক্রোনাস অপারেশন ( SyncMethodC ) অনুসরণকারী কোডটি সংরক্ষণ করা হবে।
  5. দ্বিতীয় অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপ শেষ হওয়ার পরে, SyncMethodC কার্যকর করা হবে, তারপরে সম্পাদন করা হবে এবং তৃতীয় অ্যাসিঙ্ক্রোনাস অপারেশনের জন্য অপেক্ষা করা await Task.Delay(3000)


এটি একটি সরলীকৃত ব্যাখ্যা, কারণ আসলে async/await হল সিনট্যাকটিক সুগার যাতে অ্যাসিঙ্ক্রোনাস পদ্ধতির সুবিধাজনক কলিং এবং তাদের সমাপ্তির জন্য অপেক্ষা করা যায়।


আপনি 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

C# কম্পাইলার async/await কলগুলিকে একটি IAsyncStateMachine স্টেট মেশিনে রূপান্তরিত করে, যা অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপ সম্পূর্ণ করার জন্য সঞ্চালিত ক্রিয়াগুলির একটি ক্রমিক সেট।


প্রতিবার যখন আপনি একটি ওয়েট অপারেশন কল করেন, স্টেট মেশিনটি তার কাজ শেষ করে এবং সেই অপারেশনটি সম্পূর্ণ হওয়ার জন্য অপেক্ষা করে, এর পরে এটি পরবর্তী অপারেশনটি চালিয়ে যেতে থাকে। এটি মূল থ্রেড ব্লক না করেই পটভূমিতে অ্যাসিঙ্ক্রোনাস অপারেশনগুলি সম্পাদন করার অনুমতি দেয় এবং অ্যাসিঙ্ক্রোনাস পদ্ধতির কলগুলিকে আরও সহজ এবং আরও পাঠযোগ্য করে তোলে।


এইভাবে, Example পদ্ধতিটি টীকা [AsyncStateMachine(typeof(ExampleStateMachine))] সহ একটি স্টেট মেশিন তৈরি এবং আরম্ভ করার মধ্যে রূপান্তরিত হয়, এবং স্টেট মেশিনে অপেক্ষাকৃত কলের সংখ্যার সমান রাজ্য রয়েছে।


  • রূপান্তরিত পদ্ধতির 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; }


  • একটি জেনারেট করা স্টেট মেশিনের উদাহরণ 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) { /*...*/ } }

সিঙ্ক্রোনাইজেশন প্রসঙ্গ

AwaitUnsafeOnCompleted কলে, বর্তমান সিঙ্ক্রোনাইজেশন প্রসঙ্গ SynchronizationContext প্রাপ্ত হবে। SynchronizationContext হল C#-এর একটি ধারণা যা একটি প্রসঙ্গ উপস্থাপন করতে ব্যবহৃত হয় যা অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপগুলির একটি সেটের সম্পাদন নিয়ন্ত্রণ করে। এটি একাধিক থ্রেড জুড়ে কোড কার্যকর করার সমন্বয় করতে এবং একটি নির্দিষ্ট ক্রমে কোডটি কার্যকর করা হয়েছে তা নিশ্চিত করতে ব্যবহৃত হয়। SynchronizationContext-এর মূল উদ্দেশ্য হল একটি মাল্টিথ্রেডেড পরিবেশে অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপগুলির সময়সূচী এবং সঞ্চালন নিয়ন্ত্রণ করার একটি উপায় প্রদান করা।


বিভিন্ন পরিবেশে, SynchronizationContext এর বিভিন্ন বাস্তবায়ন রয়েছে। উদাহরণস্বরূপ, .NET-তে, আছে:


  • WPF : System.Windows.Threading.DispatcherSynchronizationContext
  • WinForms : System.Windows.Forms.WindowsFormsSynchronizationContext Context
  • WinRT : System.Threading.WinRTSynchronizationContext
  • ASP.NET : System.Web.AspNetSynchronizationContext


ইউনিটির নিজস্ব সিঙ্ক্রোনাইজেশন প্রসঙ্গও রয়েছে, UnitySynchronizationContext , যা আমাদেরকে PlayerLoop API-তে আবদ্ধ করার সাথে অ্যাসিঙ্ক্রোনাস অপারেশনগুলি ব্যবহার করতে সক্ষম করে। নিম্নলিখিত কোড উদাহরণ দেখায় কিভাবে প্রতিটি ফ্রেমে একটি বস্তু ঘোরানো Task.Yield() ব্যবহার করে:

 private async void Start() { while (true) { transform.Rotate(0, Time.deltaTime * 50, 0); await Task.Yield(); } }


একটি নেটওয়ার্ক অনুরোধ করতে ইউনিটিতে অ্যাসিঙ্ক/ওয়েট ব্যবহার করার আরেকটি উদাহরণ:

 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; } } }


UnitySynchronizationContext এর জন্য ধন্যবাদ, আমরা নিরাপদে UnityEngine পদ্ধতিগুলি ব্যবহার করতে পারি (যেমন Debug.Log() ) একটি অ্যাসিঙ্ক্রোনাস অপারেশন সম্পূর্ণ হওয়ার পরেই, যেহেতু এই কোডটির কার্যকরীকরণ মূল ইউনিটি থ্রেডে চলতে থাকবে।

টাস্ক কমপ্লিটেশন সোর্স<T>

এই ক্লাসটি আপনাকে একটি Task অবজেক্ট পরিচালনা করতে দেয়। এটি টিএপি-তে পুরানো অ্যাসিঙ্ক্রোনাস পদ্ধতিগুলিকে খাপ খাইয়ে নেওয়ার জন্য তৈরি করা হয়েছিল, তবে এটিও খুব কার্যকর যখন আমরা কিছু দীর্ঘ-চলমান অপারেশনের চারপাশে একটি Task মোড়ানো চাই যা কিছু ইভেন্টে।


নিম্নলিখিত উদাহরণে, taskCompletionSource ভিতরে Task অবজেক্ট শুরু থেকে 3 সেকেন্ড পরে সম্পূর্ণ হবে, এবং আমরা 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); } }

বাতিলকরণ টোকেন

একটি বাতিলকরণ টোকেন সি# এ ব্যবহার করা হয় সংকেত দিতে যে একটি টাস্ক বা অপারেশন বাতিল করা উচিত। টোকেনটি টাস্ক বা অপারেশনে পাস করা হয় এবং টাস্ক বা অপারেশনের মধ্যে থাকা কোড টাস্ক বা অপারেশন বন্ধ করা উচিত কিনা তা নির্ধারণ করতে পর্যায়ক্রমে টোকেন পরীক্ষা করতে পারে। এটি আকস্মিকভাবে হত্যা করার পরিবর্তে একটি টাস্ক বা অপারেশনকে পরিষ্কার এবং সুন্দরভাবে বাতিল করার অনুমতি দেয়।


বাতিলকরণ টোকেনগুলি সাধারণত এমন পরিস্থিতিতে ব্যবহার করা হয় যেখানে ব্যবহারকারীর দ্বারা একটি দীর্ঘমেয়াদী কাজ বাতিল করা যেতে পারে, বা যদি টাস্কটির আর প্রয়োজন না হয়, যেমন একটি ব্যবহারকারী ইন্টারফেসে একটি বাতিল বোতাম।


সামগ্রিক প্যাটার্নটি 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(); } }


যখন অপারেশন বাতিল করা হয়, একটি OperationCanceledException নিক্ষেপ করা হবে এবং Task.IsCanceled সম্পত্তি true সেট করা হবে।

ইউনিটি 2022.2-এ নতুন অ্যাসিঙ্ক বৈশিষ্ট্য

এটি লক্ষ্য করা গুরুত্বপূর্ণ যে Task অবজেক্টগুলি .NET রানটাইম দ্বারা পরিচালিত হয়, ইউনিটি দ্বারা নয়, এবং যদি টাস্কটি সম্পাদনকারী অবজেক্টটি ধ্বংস হয়ে যায় (অথবা যদি এডিটরে গেমটি প্লে মোড থেকে বেরিয়ে যায়), টাস্কটি ইউনিটির মতো চলতে থাকবে এটা বাতিল করার কোন উপায় নেই।


আপনাকে সর্বদা সংশ্লিষ্ট CancellationToken সাথে await Task সাথে থাকতে হবে। এটি কোডের কিছু অপ্রয়োজনীয়তার দিকে নিয়ে যায়, এবং ইউনিটি 2022.2-এ MonoBehaviour স্তরে অন্তর্নির্মিত টোকেন এবং সম্পূর্ণ Application স্তর উপস্থিত হয়।


চলুন দেখি 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); } } }


আমাদের আর ম্যানুয়ালি একটি CancellationTokenSource তৈরি করতে হবে না এবং OnDestroy পদ্ধতিতে কাজটি সম্পূর্ণ করতে হবে। একটি নির্দিষ্ট MonoBehaviour এর সাথে যুক্ত নয় এমন কাজের জন্য, আমরা UnityEngine.Application.exitCancellationToken ব্যবহার করতে পারি। প্লে মোড (এডিটরে) থেকে বের হওয়ার সময় বা অ্যাপ্লিকেশান ছেড়ে দেওয়ার সময় এটি কাজটি বন্ধ করে দেবে।

ইউনিটাস্ক

ব্যবহার করার সুবিধা এবং .NET টাস্ক দ্বারা প্রদত্ত ক্ষমতা থাকা সত্ত্বেও, ইউনিটিতে ব্যবহার করার সময় তাদের উল্লেখযোগ্য ত্রুটি রয়েছে:


  • Task অবজেক্ট খুব কষ্টকর এবং অনেক বরাদ্দ কারণ.
  • Task ইউনিটি থ্রেডিং (একক-থ্রেড) এর সাথে মেলে না।


UniTask লাইব্রেরি থ্রেড বা SynchronizationContext ব্যবহার না করেই এই সীমাবদ্ধতাগুলিকে বাইপাস করে। এটি UniTask<T> struct-ভিত্তিক প্রকার ব্যবহার করে বরাদ্দের অনুপস্থিতি অর্জন করে।


UniTask-এর জন্য .NET 4.x স্ক্রিপ্টিং রানটাইম সংস্করণ প্রয়োজন, ইউনিটি 2018.4.13f1 হল অফিসিয়াল সর্বনিম্ন সমর্থিত সংস্করণ।


এছাড়াও আপনি এক্সটেনশন পদ্ধতি সহ সমস্ত 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 } } }


এই উদাহরণে, LoadAsset পদ্ধতি অ্যাসিঙ্ক্রোনাসভাবে একটি সম্পদ লোড করতে Resources.LoadAsync ব্যবহার করে। AsUniTask পদ্ধতিটি LoadAsync দ্বারা ফিরে আসা AsyncOperation UniTask এ রূপান্তর করতে ব্যবহৃত হয়, যা অপেক্ষা করা যেতে পারে।


আগের মতই, আপনি 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-এ, UniTaskSynchronizationContext নামে SynchronizationContext কনটেক্সট-এর আরেকটি বাস্তবায়ন রয়েছে যা আরও ভাল পারফরম্যান্সের জন্য UnitySynchronizationContext প্রতিস্থাপন করতে ব্যবহার করা যেতে পারে।

প্রতীক্ষিত API

ইউনিটি 2023.1 এর প্রথম আলফা সংস্করণে, Awaitable ক্লাস চালু করা হয়েছিল। প্রতীক্ষিত Coroutines হল async/await-compatible Task-এর মতন যা Unity-এ চালানোর জন্য ডিজাইন করা হয়েছে। .NET টাস্কের বিপরীতে, এগুলি ইঞ্জিন দ্বারা পরিচালিত হয়, রানটাইম নয়।

 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"); // ... }


এগুলি অপেক্ষা করা যেতে পারে এবং একটি অ্যাসিঙ্ক পদ্ধতির রিটার্ন টাইপ হিসাবে ব্যবহার করা যেতে পারে। System.Threading.Tasks এর তুলনায়, এগুলি কম পরিশীলিত কিন্তু ইউনিটি-নির্দিষ্ট অনুমানের উপর ভিত্তি করে কর্মক্ষমতা বৃদ্ধিকারী শর্টকাটগুলি গ্রহণ করে৷


এখানে .NET টাস্কের তুলনায় প্রধান পার্থক্য রয়েছে:


  • Awaitable বস্তু শুধুমাত্র একবার অপেক্ষা করা যেতে পারে; এটি একাধিক অ্যাসিঙ্ক ফাংশন দ্বারা অপেক্ষা করা যাবে না।
  • Awaiter.GetResults() সম্পূর্ণ না হওয়া পর্যন্ত ব্লক করা হবে না। অপারেশন শেষ হওয়ার আগে এটিকে কল করা অনির্ধারিত আচরণ।
  • কখনও একটি ExecutionContext ক্যাপচার করবেন না। নিরাপত্তার কারণে, .NET টাস্কগুলি অ্যাসিঙ্ক্রোনাস কলগুলিতে ছদ্মবেশী প্রসঙ্গ প্রচার করার জন্য অপেক্ষা করার সময় এক্সিকিউশন প্রসঙ্গগুলি ক্যাপচার করে৷
  • কখনই SynchronizationContext ক্যাপচার করবেন না। কোরাউটিন ধারাবাহিকতাগুলি সমাপ্তি বাড়ায় এমন কোড থেকে সিঙ্ক্রোনাসভাবে কার্যকর করা হয়। বেশিরভাগ ক্ষেত্রে, এটি ইউনিটি প্রধান ফ্রেম থেকে হবে।
  • অত্যধিক বরাদ্দ প্রতিরোধ করার জন্য প্রতীক্ষিত বস্তুগুলি পুল করা হয়৷ এগুলি হল রেফারেন্সের ধরন, তাই এগুলিকে বিভিন্ন স্ট্যাক জুড়ে উল্লেখ করা যেতে পারে, দক্ষতার সাথে অনুলিপি করা যেতে পারে এবং আরও অনেক কিছু। অ্যাসিঙ্ক স্টেট মেশিনের দ্বারা তৈরি সাধারণ গেট/রিলিজ সিকোয়েন্সে Stack<T> বাউন্ড চেক এড়াতে ObjectPool উন্নত করা হয়েছে।


একটি দীর্ঘ অপারেশনের ফলাফল পেতে, আপনি Awaitable<T> প্রকার ব্যবহার করতে পারেন। আপনি AwaitableCompletionSource এবং AwaitableCompletionSource<T> ব্যবহার করে একটি Awaitable এর সমাপ্তি পরিচালনা করতে পারেন , TaskCompletitionSource এর মতো:

 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); } }


কখনও কখনও এটি বিশাল গণনা সঞ্চালন করা প্রয়োজন যা গেম হিমায়িত হতে পারে। এর জন্য, অপেক্ষাযোগ্য পদ্ধতিগুলি ব্যবহার করা ভাল: 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); }


এইভাবে, Awaitables .NET টাস্ক ব্যবহার করার ত্রুটিগুলি দূর করে এবং PlayerLoop ইভেন্ট এবং AsyncOperations অপেক্ষা করার অনুমতি দেয়।

উপসংহার

আমরা দেখতে পাচ্ছি, ইউনিটির বিকাশের সাথে, অ্যাসিঙ্ক্রোনাস অপারেশনগুলি সংগঠিত করার জন্য আরও বেশি সংখ্যক সরঞ্জাম রয়েছে:

ঐক্য

করুটিন

প্রতিশ্রুতি

.NET কার্য

ইউনিটাস্ক

অন্তর্নির্মিত বাতিলকরণ টোকেন

প্রতীক্ষিত API

5.6





2017.1




2018.4



2022.2


2023.1


আমরা ইউনিটিতে অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংয়ের সমস্ত প্রধান উপায় বিবেচনা করেছি। আপনার টাস্কের জটিলতা এবং ইউনিটির যে সংস্করণটি আপনি ব্যবহার করছেন তার উপর নির্ভর করে, আপনি আপনার গেমগুলিতে একটি মসৃণ এবং নির্বিঘ্ন গেমপ্লে নিশ্চিত করতে Coroutines এবং প্রতিশ্রুতি থেকে কার্য এবং প্রতীক্ষিত পর্যন্ত বিস্তৃত প্রযুক্তি ব্যবহার করতে পারেন। পড়ার জন্য ধন্যবাদ, এবং আমরা আপনার পরবর্তী মাস্টারপিসের জন্য অপেক্ষা করছি।