WebAssembly থেকে অ্যাসিঙ্ক্রোনাস ওয়েব API ব্যবহার করা

ওয়েবে I/O APIগুলি অ্যাসিঙ্ক্রোনাস, কিন্তু বেশিরভাগ সিস্টেম ভাষায় তারা সিঙ্ক্রোনাস। WebAssembly-এ কোড কম্পাইল করার সময়, আপনাকে এক ধরনের API-এর সাথে আরেকটা ব্রিজ করতে হবে—এবং এই ব্রিজটি হল Asyncify। এই পোস্টে, আপনি শিখবেন কখন এবং কীভাবে Asyncify ব্যবহার করবেন এবং কীভাবে এটি হুডের অধীনে কাজ করে।

সিস্টেমের ভাষায় I/O

আমি সি-তে একটি সাধারণ উদাহরণ দিয়ে শুরু করব। বলুন, আপনি একটি ফাইল থেকে ব্যবহারকারীর নাম পড়তে চান এবং "হ্যালো, (ব্যবহারকারীর নাম)!" দিয়ে তাদের শুভেচ্ছা জানান। বার্তা:

#include <stdio.h>

int main() {
    FILE *stream = fopen("name.txt", "r");
    char name[20+1];
    size_t len = fread(&name, 1, 20, stream);
    name[len] = '\0';
    fclose(stream);
    printf("Hello, %s!\n", name);
    return 0;
}

যদিও উদাহরণটি খুব বেশি কিছু করে না, এটি ইতিমধ্যেই এমন কিছু প্রদর্শন করে যা আপনি যে কোনও আকারের অ্যাপ্লিকেশনে পাবেন: এটি বাহ্যিক বিশ্বের কিছু ইনপুট পড়ে, অভ্যন্তরীণভাবে সেগুলিকে প্রক্রিয়া করে এবং আউটপুটগুলিকে বাহ্যিক বিশ্বে ফেরত পাঠায়। বাইরের বিশ্বের সাথে এই ধরনের সমস্ত মিথস্ক্রিয়া কিছু ফাংশনের মাধ্যমে ঘটে যাকে সাধারণত ইনপুট-আউটপুট ফাংশন বলা হয়, যাকে I/O তে সংক্ষিপ্ত করা হয়।

C থেকে নাম পড়ার জন্য, আপনার কমপক্ষে দুটি গুরুত্বপূর্ণ I/O কল দরকার: fopen , ফাইলটি খুলতে এবং এটি থেকে ডেটা পড়ার জন্য fread । একবার আপনি ডেটা পুনরুদ্ধার করার পরে, আপনি কনসোলে ফলাফল প্রিন্ট করতে অন্য I/O ফাংশন printf ব্যবহার করতে পারেন।

এই ফাংশনগুলি প্রথম নজরে বেশ সহজ দেখায় এবং ডেটা পড়তে বা লিখতে জড়িত যন্ত্রপাতি সম্পর্কে আপনাকে দুবার ভাবতে হবে না। যাইহোক, পরিবেশের উপর নির্ভর করে, ভিতরে অনেক কিছু ঘটতে পারে:

  • ইনপুট ফাইলটি স্থানীয় ড্রাইভে অবস্থিত থাকলে, ফাইলটি সনাক্ত করতে অ্যাপ্লিকেশনটিকে মেমরি এবং ডিস্ক অ্যাক্সেসের একটি সিরিজ সঞ্চালন করতে হবে, অনুমতি পরীক্ষা করতে হবে, পড়ার জন্য এটি খুলতে হবে এবং তারপরে অনুরোধকৃত সংখ্যক বাইট পুনরুদ্ধার না হওয়া পর্যন্ত ব্লক দ্বারা ব্লক পড়তে হবে। . আপনার ডিস্কের গতি এবং অনুরোধ করা আকারের উপর নির্ভর করে এটি বেশ ধীর হতে পারে।
  • অথবা, ইনপুট ফাইলটি একটি মাউন্ট করা নেটওয়ার্ক অবস্থানে অবস্থিত হতে পারে, এই ক্ষেত্রে, নেটওয়ার্ক স্ট্যাকটিও এখন জড়িত থাকবে, জটিলতা, বিলম্বিতা এবং প্রতিটি অপারেশনের জন্য সম্ভাব্য পুনঃপ্রচারের সংখ্যা বৃদ্ধি করবে।
  • অবশেষে, এমনকি printf জিনিসগুলি কনসোলে প্রিন্ট করার গ্যারান্টি দেওয়া হয় না এবং এটি একটি ফাইল বা নেটওয়ার্ক অবস্থানে পুনঃনির্দেশিত হতে পারে, এই ক্ষেত্রে এটিকে উপরের একই পদক্ষেপগুলি দিয়ে যেতে হবে।

দীর্ঘ গল্প সংক্ষেপে, I/O ধীর হতে পারে এবং আপনি কোডের দিকে এক নজরে একটি নির্দিষ্ট কল কতক্ষণ সময় নেবে তা অনুমান করতে পারবেন না। সেই অপারেশন চলাকালীন, আপনার পুরো অ্যাপ্লিকেশনটি হিমায়িত এবং ব্যবহারকারীর কাছে প্রতিক্রিয়াহীন দেখাবে৷

এটি C বা C++ এর মধ্যেও সীমাবদ্ধ নয়। বেশিরভাগ সিস্টেম ল্যাঙ্গুয়েজ সমস্ত I/O সিঙ্ক্রোনাস API-এর আকারে উপস্থাপন করে। উদাহরণ স্বরূপ, আপনি যদি উদাহরণটিকে Rust-এ অনুবাদ করেন, তাহলে APIটি সহজ দেখাতে পারে, কিন্তু একই নীতি প্রযোজ্য। আপনি শুধু একটি কল করুন এবং সিঙ্ক্রোনাসভাবে ফলাফলটি ফেরত দেওয়ার জন্য অপেক্ষা করুন, যখন এটি সমস্ত ব্যয়বহুল ক্রিয়াকলাপ সম্পাদন করে এবং অবশেষে একটি একক আহ্বানে ফলাফলটি ফেরত দেয়:

fn main() {
    let s = std::fs::read_to_string("name.txt");
    println!("Hello, {}!", s);
}

কিন্তু যখন আপনি WebAssembly-তে সেই নমুনাগুলির মধ্যে যেকোনও কম্পাইল করার চেষ্টা করেন এবং ওয়েবে অনুবাদ করেন তখন কী হয়? অথবা, একটি নির্দিষ্ট উদাহরণ প্রদান করতে, "ফাইল রিড" অপারেশন কি অনুবাদ করতে পারে? এটি কিছু স্টোরেজ থেকে ডেটা পড়তে হবে।

ওয়েবের অ্যাসিঙ্ক্রোনাস মডেল

ওয়েবে বিভিন্ন ধরণের স্টোরেজ বিকল্প রয়েছে যা আপনি ম্যাপ করতে পারেন, যেমন ইন-মেমরি স্টোরেজ (JS অবজেক্ট), localStorage , IndexedDB , সার্ভার-সাইড স্টোরেজ, এবং একটি নতুন ফাইল সিস্টেম অ্যাক্সেস API

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

এটি ওয়েবে কোড কার্যকর করার মূল বৈশিষ্ট্যগুলির মধ্যে একটি: যেকোন সময়-সাপেক্ষ ক্রিয়াকলাপ, যার মধ্যে যেকোন I/O রয়েছে, অসিঙ্ক্রোনাস হতে হবে।

কারণ হল যে ওয়েব ঐতিহাসিকভাবে একক-থ্রেডেড, এবং UI স্পর্শ করে এমন যেকোনো ব্যবহারকারী কোডকে UI-এর মতো একই থ্রেডে চলতে হবে। এটি সিপিইউ সময়ের জন্য লেআউট, রেন্ডারিং এবং ইভেন্ট পরিচালনার মতো অন্যান্য গুরুত্বপূর্ণ কাজের সাথে প্রতিযোগিতা করতে হবে। আপনি জাভাস্ক্রিপ্ট বা WebAssembly-এর একটি টুকরা একটি "ফাইল রিড" অপারেশন শুরু করতে সক্ষম হতে চান না এবং অন্য সব কিছুকে ব্লক করতে পারেন—সম্পূর্ণ ট্যাব, বা, অতীতে, সম্পূর্ণ ব্রাউজার—মিলিসেকেন্ড থেকে কয়েক সেকেন্ডের জন্য , এটা শেষ না হওয়া পর্যন্ত।

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

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

এই প্রক্রিয়া সম্পর্কে মনে রাখা গুরুত্বপূর্ণ বিষয় হল যে, আপনার কাস্টম জাভাস্ক্রিপ্ট (বা WebAssembly) কোড কার্যকর করার সময়, ইভেন্ট লুপটি ব্লক করা হয় এবং এটি থাকাকালীন, কোনও বহিরাগত হ্যান্ডলার, ইভেন্ট, I/O-তে প্রতিক্রিয়া জানানোর কোনও উপায় নেই। ইত্যাদি। I/O ফলাফলগুলি ফিরে পাওয়ার একমাত্র উপায় হল একটি কলব্যাক নিবন্ধন করা, আপনার কোড কার্যকর করা শেষ করা এবং ব্রাউজারকে নিয়ন্ত্রণ ফিরিয়ে দেওয়া যাতে এটি কোনো মুলতুবি থাকা কাজগুলিকে প্রক্রিয়াজাত করতে পারে। একবার I/O শেষ হয়ে গেলে, আপনার হ্যান্ডলার সেই কাজগুলির মধ্যে একটি হয়ে উঠবে এবং কার্যকর করা হবে।

উদাহরণস্বরূপ, যদি আপনি আধুনিক জাভাস্ক্রিপ্টে উপরের নমুনাগুলি পুনরায় লিখতে চান এবং একটি দূরবর্তী URL থেকে একটি নাম পড়ার সিদ্ধান্ত নেন, তাহলে আপনি Fetch API এবং async-await সিনট্যাক্স ব্যবহার করবেন:

async function main() {
  let response = await fetch("name.txt");
  let name = await response.text();
  console.log("Hello, %s!", name);
}

যদিও এটি সিঙ্ক্রোনাস দেখায়, হুডের নীচে প্রতিটি await মূলত কলব্যাকের জন্য সিনট্যাক্স চিনি:

function main() {
  return fetch("name.txt")
    .then(response => response.text())
    .then(name => console.log("Hello, %s!", name));
}

এই ডি-সুগারড উদাহরণে, যা একটু পরিষ্কার, একটি অনুরোধ শুরু হয়েছে এবং প্রতিক্রিয়াগুলি প্রথম কলব্যাকের সাথে সদস্যতা নেওয়া হয়েছে৷ একবার ব্রাউজার প্রাথমিক প্রতিক্রিয়া-শুধুমাত্র HTTP শিরোনামগুলি পায়-এটি অ্যাসিঙ্ক্রোনাসভাবে এই কলব্যাককে আহ্বান করে। কলব্যাক response.text() ব্যবহার করে টেক্সট হিসেবে বডি পড়া শুরু করে এবং অন্য কলব্যাকের সাথে ফলাফলে সদস্যতা নেয়। অবশেষে, একবার fetch সমস্ত বিষয়বস্তু পুনরুদ্ধার করা হলে, এটি শেষ কলব্যাককে আহ্বান করে, যা "হ্যালো, (ব্যবহারকারীর নাম)!" কনসোলে

এই পদক্ষেপগুলির অ্যাসিঙ্ক্রোনাস প্রকৃতির জন্য ধন্যবাদ, মূল ফাংশনটি I/O নির্ধারিত হওয়ার সাথে সাথে ব্রাউজারে নিয়ন্ত্রণ ফিরিয়ে দিতে পারে এবং সম্পূর্ণ UI রেসপন্সিভ ছেড়ে দিতে পারে এবং রেন্ডারিং, স্ক্রলিং ইত্যাদি সহ অন্যান্য কাজের জন্য উপলব্ধ থাকতে পারে। I/O ব্যাকগ্রাউন্ডে কার্যকর হচ্ছে।

একটি চূড়ান্ত উদাহরণ হিসাবে, এমনকি "স্লিপ" এর মতো সাধারণ API, যা একটি অ্যাপ্লিকেশনকে একটি নির্দিষ্ট সংখ্যক সেকেন্ড অপেক্ষা করে, এটিও একটি I/O অপারেশনের একটি রূপ:

#include <stdio.h>
#include <unistd.h>
// ...
printf("A\n");
sleep(1);
printf("B\n");

অবশ্যই, আপনি এটিকে খুব সহজবোধ্যভাবে অনুবাদ করতে পারেন যা সময়ের মেয়াদ শেষ না হওয়া পর্যন্ত বর্তমান থ্রেডটিকে ব্লক করবে:

console.log("A");
for (let start = Date.now(); Date.now() - start < 1000;);
console.log("B");

প্রকৃতপক্ষে, এমস্ক্রিপ্টেন "স্লিপ" এর ডিফল্ট বাস্তবায়নে ঠিক এটিই করে, তবে এটি খুব অকার্যকর, পুরো UI ব্লক করবে এবং এর মধ্যে অন্য কোনও ইভেন্ট পরিচালনা করার অনুমতি দেবে না। সাধারণত, উত্পাদন কোডে এটি করবেন না।

পরিবর্তে, জাভাস্ক্রিপ্টে "sleep"-এর আরও একটি মূর্খ সংস্করণ setTimeout() কল করা এবং হ্যান্ডলারের সাথে সদস্যতা নেওয়া জড়িত:

console.log("A");
setTimeout(() => {
    console.log("B");
}, 1000);

এই সব উদাহরণ এবং এপিআই সাধারণ কি? প্রতিটি ক্ষেত্রে, মূল সিস্টেমের ভাষায় বাহাদুরি কোড I/O-এর জন্য একটি ব্লকিং API ব্যবহার করে, যেখানে ওয়েবের জন্য একটি সমতুল্য উদাহরণ পরিবর্তে একটি অ্যাসিঙ্ক্রোনাস API ব্যবহার করে। ওয়েবে কম্পাইল করার সময়, আপনাকে সেই দুটি এক্সিকিউশন মডেলের মধ্যে কোনো না কোনোভাবে রূপান্তর করতে হবে এবং WebAssembly-এর এখনও তা করার কোনো অন্তর্নির্মিত ক্ষমতা নেই।

Asyncify দিয়ে ব্যবধান পূরণ করা

এখানেই Asyncify আসে। Asyncify হল Emscripten দ্বারা সমর্থিত একটি কম্পাইল-টাইম বৈশিষ্ট্য যা সম্পূর্ণ প্রোগ্রামকে বিরাম দিতে এবং পরে অ্যাসিঙ্ক্রোনাসভাবে পুনরায় শুরু করতে দেয়।

একটি জাভাস্ক্রিপ্ট -> WebAssembly -> web API -> async টাস্ক ইনভোকেশন বর্ণনা করে একটি কল গ্রাফ, যেখানে Asyncify অ্যাসিঙ্ক টাস্কের ফলাফলকে আবার WebAssembly-এ সংযুক্ত করে

Emscripten-এর সাথে C/C++-এ ব্যবহার

আপনি যদি শেষ উদাহরণের জন্য একটি অ্যাসিঙ্ক্রোনাস স্লিপ বাস্তবায়ন করতে Asyncify ব্যবহার করতে চান তবে আপনি এটি এভাবে করতে পারেন:

#include <stdio.h>
#include <emscripten.h>

EM_JS(void, async_sleep, (int seconds), {
    Asyncify.handleSleep(wakeUp => {
        setTimeout(wakeUp, seconds * 1000);
    });
});
…
puts("A");
async_sleep(1);
puts("B");

EM_JS হল একটি ম্যাক্রো যা জাভাস্ক্রিপ্টের স্নিপেটগুলিকে C ফাংশন হিসাবে সংজ্ঞায়িত করতে দেয়৷ ভিতরে, একটি ফাংশন Asyncify.handleSleep() ব্যবহার করুন যা Emscripten কে প্রোগ্রাম স্থগিত করতে বলে এবং একটি wakeUp() হ্যান্ডলার প্রদান করে যা অ্যাসিঙ্ক্রোনাস অপারেশন শেষ হলে কল করা উচিত। উপরের উদাহরণে, হ্যান্ডলারটিকে setTimeout() এ পাস করা হয়েছে, কিন্তু এটি অন্য কোনো প্রসঙ্গে ব্যবহার করা যেতে পারে যা কলব্যাক গ্রহণ করে। অবশেষে, আপনি যেকোন জায়গায় async_sleep() কল করতে পারেন ঠিক রেগুলার sleep() বা অন্য কোন সিঙ্ক্রোনাস API এর মত।

এই ধরনের কোড কম্পাইল করার সময়, আপনাকে Asyncify বৈশিষ্ট্যটি সক্রিয় করতে Emscripten কে বলতে হবে। -s ASYNCIFY পাশাপাশি -s ASYNCIFY_IMPORTS=[func1, func2] ফাংশনগুলির একটি অ্যারের মতো তালিকা যা অ্যাসিঙ্ক্রোনাস হতে পারে পাস করে এটি করুন।

emcc -O2 \
    -s ASYNCIFY \
    -s ASYNCIFY_IMPORTS=[async_sleep] \
    ...

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

এখন, যখন আপনি ব্রাউজারে এই কোডটি কার্যকর করবেন তখন আপনি একটি বিরামবিহীন আউটপুট লগ দেখতে পাবেন যেমনটি আপনি আশা করেছিলেন, A এর পরে অল্প বিলম্বের পরে B আসবে।

A
B

আপনি Asyncify ফাংশন থেকেও মান ফেরত দিতে পারেন। আপনাকে যা করতে হবে তা হল handleSleep() এর ফলাফলটি ফেরত দিন এবং ফলাফলটি wakeUp() কলব্যাকে পাস করুন। উদাহরণস্বরূপ, যদি, একটি ফাইল থেকে পড়ার পরিবর্তে, আপনি একটি দূরবর্তী সংস্থান থেকে একটি নম্বর আনতে চান, আপনি একটি অনুরোধ ইস্যু করতে, সি কোডটি স্থগিত করতে এবং প্রতিক্রিয়া বডি পুনরুদ্ধার করার পরে পুনরায় শুরু করতে নীচের একটির মতো একটি স্নিপেট ব্যবহার করতে পারেন —সবই নির্বিঘ্নে করা হয়েছে যেন কলটি সিঙ্ক্রোনাস।

EM_JS(int, get_answer, (), {
     return Asyncify.handleSleep(wakeUp => {
        fetch("answer.txt")
            .then(response => response.text())
            .then(text => wakeUp(Number(text)));
    });
});
puts("Getting answer...");
int answer = get_answer();
printf("Answer is %d\n", answer);

প্রকৃতপক্ষে, fetch() এর মত প্রতিশ্রুতি-ভিত্তিক APIগুলির জন্য, আপনি কলব্যাক-ভিত্তিক API ব্যবহার করার পরিবর্তে JavaScript-এর async-await বৈশিষ্ট্যের সাথে Asyncify-কে একত্রিত করতে পারেন। তার জন্য Asyncify.handleSleep() এর পরিবর্তে Asyncify.handleAsync() কল করুন। তারপরে, একটি wakeUp() কলব্যাকের সময়সূচী করার পরিবর্তে, আপনি একটি async JavaScript ফাংশন পাস করতে পারেন এবং ভিতরে await এবং return ব্যবহার করতে পারেন, কোডটিকে আরও বেশি স্বাভাবিক এবং সিঙ্ক্রোনাস দেখায়, যেখানে অ্যাসিঙ্ক্রোনাস I/O-এর কোনো সুবিধা হারাবেন না।

EM_JS(int, get_answer, (), {
     return Asyncify.handleAsync(async () => {
        let response = await fetch("answer.txt");
        let text = await response.text();
        return Number(text);
    });
});

int answer = get_answer();

জটিল মান অপেক্ষা করছে

কিন্তু এই উদাহরণটি এখনও আপনাকে শুধুমাত্র সংখ্যার মধ্যে সীমাবদ্ধ করে। যদি আপনি মূল উদাহরণটি বাস্তবায়ন করতে চান, যেখানে আমি একটি স্ট্রিং হিসাবে একটি ফাইল থেকে ব্যবহারকারীর নাম পেতে চেষ্টা করেছি? আচ্ছা, আপনি এটাও করতে পারেন!

Emscripten এম্বিন্ড নামে একটি বৈশিষ্ট্য প্রদান করে যা আপনাকে জাভাস্ক্রিপ্ট এবং C++ মানগুলির মধ্যে রূপান্তরগুলি পরিচালনা করতে দেয়। এটি Asyncify-এর জন্যও সমর্থন রয়েছে, তাই আপনি বহিরাগত Promise s-এ await() কল করতে পারেন এবং এটি async-await JavaScript কোডের মতোই await করবে:

val fetch = val::global("fetch");
val response = fetch(std::string("answer.txt")).await();
val text = response.call<val>("text").await();
auto answer = text.as<std::string>();

এই পদ্ধতিটি ব্যবহার করার সময়, আপনাকে একটি কম্পাইল পতাকা হিসাবে ASYNCIFY_IMPORTS পাস করতে হবে না, কারণ এটি ইতিমধ্যেই ডিফল্টরূপে অন্তর্ভুক্ত রয়েছে৷

ঠিক আছে, তাই এই সব Emscripten মহান কাজ করে. অন্যান্য টুলচেইন এবং ভাষা সম্পর্কে কি?

অন্যান্য ভাষা থেকে ব্যবহার

বলুন যে আপনার মরিচা কোডের কোথাও আপনার একটি অনুরূপ সিঙ্ক্রোনাস কল রয়েছে যা আপনি ওয়েবে একটি অ্যাসিঙ্ক API এ ম্যাপ করতে চান৷ দেখা যাচ্ছে, আপনিও তা করতে পারেন!

প্রথমত, আপনাকে extern ব্লক (বা বিদেশী ফাংশনের জন্য আপনার নির্বাচিত ভাষার সিনট্যাক্স) মাধ্যমে একটি নিয়মিত আমদানি হিসাবে এই ধরনের ফাংশনকে সংজ্ঞায়িত করতে হবে।

extern {
    fn get_answer() -> i32;
}

println!("Getting answer...");
let answer = get_answer();
println!("Answer is {}", answer);

এবং আপনার কোড WebAssembly এ কম্পাইল করুন:

cargo build --target wasm32-unknown-unknown

এখন আপনাকে স্ট্যাক সংরক্ষণ/পুনরুদ্ধার করার জন্য কোড সহ WebAssembly ফাইলটিকে ইনস্ট্রুমেন্ট করতে হবে। C/C++ এর জন্য, Emscripten আমাদের জন্য এটি করবে, কিন্তু এটি এখানে ব্যবহার করা হয়নি, তাই প্রক্রিয়াটি একটু বেশি ম্যানুয়াল।

ভাগ্যক্রমে, Asyncify রূপান্তর নিজেই সম্পূর্ণরূপে টুলচেন-অজ্ঞেয়বাদী। এটি নির্বিচারে WebAssembly ফাইলগুলিকে রূপান্তর করতে পারে, এটি যে কম্পাইলার দ্বারা উত্পাদিত হোক না কেন। Binaryen টুলচেইন থেকে wasm-opt optimizer-এর অংশ হিসেবে ট্রান্সফর্মটি আলাদাভাবে প্রদান করা হয়েছে এবং এটিকে এভাবে আহ্বান করা যেতে পারে:

wasm-opt -O2 --asyncify \
      --pass-arg=asyncify-imports@env.get_answer \
      [...]

ট্রান্সফর্ম সক্রিয় করতে --asyncify পাস করুন, এবং তারপর অসিঙ্ক্রোনাস ফাংশনগুলির একটি কমা-বিভাজিত তালিকা প্রদান করতে --pass-arg=… ব্যবহার করুন, যেখানে প্রোগ্রামের অবস্থা স্থগিত করা উচিত এবং পরে পুনরায় চালু করা উচিত।

যা বাকি আছে তা হল সমর্থনকারী রানটাইম কোড প্রদান করা যা আসলে তা করবে—ওয়েবঅ্যাসেম্বলি কোড সাসপেন্ড এবং পুনরায় শুরু করুন। আবার, C/C++ ক্ষেত্রে এটি Emscripten দ্বারা অন্তর্ভুক্ত করা হবে, কিন্তু এখন আপনার কাস্টম জাভাস্ক্রিপ্ট আঠালো কোড প্রয়োজন যা নির্বিচারে WebAssembly ফাইলগুলি পরিচালনা করবে। আমরা একটি লাইব্রেরি তৈরি করেছি শুধু যে জন্য.

আপনি এটিকে GitHub-এ https://github.com/GoogleChromeLabs/asyncify বা npm-এ asyncify-wasm নামে খুঁজে পেতে পারেন।

এটি একটি স্ট্যান্ডার্ড WebAssembly instantiation API অনুকরণ করে, কিন্তু তার নিজস্ব নামস্থানের অধীনে। শুধুমাত্র পার্থক্য হল, একটি নিয়মিত WebAssembly API-এর অধীনে আপনি শুধুমাত্র আমদানি হিসাবে সিঙ্ক্রোনাস ফাংশন প্রদান করতে পারেন, যখন Asyncify র‍্যাপারের অধীনে, আপনি অ্যাসিঙ্ক্রোনাস আমদানিও প্রদান করতে পারেন:

const { instance } = await Asyncify.instantiateStreaming(fetch('app.wasm'), {
    env: {
        async get_answer() {
            let response = await fetch("answer.txt");
            let text = await response.text();
            return Number(text);
        }
    }
});
…
await instance.exports.main();

একবার আপনি যেমন একটি অ্যাসিঙ্ক্রোনাস ফাংশন কল করার চেষ্টা করেন - যেমন উপরের উদাহরণে get_answer() - WebAssembly দিক থেকে, লাইব্রেরি ফিরে আসা Promise সনাক্ত করবে, WebAssembly অ্যাপ্লিকেশনটির অবস্থা স্থগিত করবে এবং সংরক্ষণ করবে, প্রতিশ্রুতি সম্পন্ন হওয়ার জন্য সদস্যতা গ্রহণ করবে এবং পরে , একবার এটি সমাধান হয়ে গেলে, নির্বিঘ্নে কল স্ট্যাক এবং স্টেট পুনরুদ্ধার করুন এবং এক্সিকিউশন চালিয়ে যান যেন কিছুই হয়নি।

যেহেতু মডিউলের যেকোন ফাংশন একটি অ্যাসিঙ্ক্রোনাস কল করতে পারে, সমস্ত রপ্তানিও সম্ভাব্য অ্যাসিঙ্ক্রোনাস হয়ে যায়, তাই সেগুলিও মোড়ানো হয়। আপনি হয়ত উপরের উদাহরণে লক্ষ্য করেছেন যে কখন কার্যকর করা সত্যিই শেষ হয়েছে তা জানতে আপনাকে instance.exports.main() এর ফলাফলের await হবে।

কিভাবে এই সব ফণা অধীনে কাজ করে?

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

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

পূর্বে দেখানো অ্যাসিঙ্ক্রোনাস ঘুমের উদাহরণ কম্পাইল করার সময়:

puts("A");
async_sleep(1);
puts("B");

Asyncify এই কোডটি নেয় এবং এটিকে মোটামুটি নিম্নলিখিতটির মতো রূপান্তরিত করে (ছদ্ম-কোড, বাস্তব রূপান্তর এর চেয়ে বেশি জড়িত):

if (mode == NORMAL_EXECUTION) {
    puts("A");
    async_sleep(1);
    saveLocals();
    mode = UNWINDING;
    return;
}
if (mode == REWINDING) {
    restoreLocals();
    mode = NORMAL_EXECUTION;
}
puts("B");

প্রাথমিকভাবে mode NORMAL_EXECUTION এ সেট করা হয়েছে। তদনুসারে, এই ধরনের রূপান্তরিত কোডটি প্রথমবার কার্যকর করা হলে, শুধুমাত্র async_sleep() এর দিকে অগ্রসর হওয়া অংশটি মূল্যায়ন করা হবে। অ্যাসিঙ্ক্রোনাস ক্রিয়াকলাপ নির্ধারিত হওয়ার সাথে সাথে, Asyncify সমস্ত লোকালকে সংরক্ষণ করে এবং প্রতিটি ফাংশন থেকে উপরের দিকে ফিরে এসে স্ট্যাকটি খুলে দেয়, এইভাবে ব্রাউজার ইভেন্ট লুপে নিয়ন্ত্রণ ফিরিয়ে দেয়।

তারপর, একবার async_sleep() সমাধান হয়ে গেলে, Asyncify সমর্থন কোড REWINDINGmode পরিবর্তন করবে এবং ফাংশনটিকে আবার কল করবে। এইবার, "স্বাভাবিক নির্বাহ" শাখাটি বাদ দেওয়া হয়েছে - যেহেতু এটি ইতিমধ্যেই গতবার কাজ করেছে এবং আমি "A" দুবার মুদ্রণ এড়াতে চাই - এবং পরিবর্তে এটি সরাসরি "রিওয়াইন্ডিং" শাখায় আসে। একবার এটি পৌঁছে গেলে, এটি সমস্ত সঞ্চিত লোকালকে পুনরুদ্ধার করে, মোডকে "স্বাভাবিক" এ পরিবর্তন করে এবং এক্সিকিউশনটি চালিয়ে যায় যেন কোডটি প্রথম স্থানে বন্ধ করা হয়নি।

রূপান্তর খরচ

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

বিভিন্ন বেঞ্চমার্কের জন্য কোড সাইজ ওভারহেড দেখানো একটি গ্রাফ, সূক্ষ্ম-সংযুক্ত অবস্থার অধীনে প্রায় 0% থেকে খারাপ ক্ষেত্রে 100% পর্যন্ত

এটি আদর্শ নয়, তবে অনেক ক্ষেত্রেই গ্রহণযোগ্য যখন বিকল্পটির কার্যকারিতা সম্পূর্ণরূপে নেই বা মূল কোডে উল্লেখযোগ্য পুনর্লিখন করতে হচ্ছে।

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

বাস্তব বিশ্বের ডেমো

এখন আপনি সাধারণ উদাহরণগুলি দেখেছেন, আমি আরও জটিল পরিস্থিতিতে চলে যাব।

নিবন্ধের শুরুতে যেমন উল্লেখ করা হয়েছে, ওয়েবে স্টোরেজ বিকল্পগুলির মধ্যে একটি হল একটি অ্যাসিঙ্ক্রোনাস ফাইল সিস্টেম অ্যাক্সেস API । এটি একটি ওয়েব অ্যাপ্লিকেশন থেকে একটি বাস্তব হোস্ট ফাইল সিস্টেমে অ্যাক্সেস প্রদান করে।

অন্যদিকে, কনসোল এবং সার্ভার-সাইডে WebAssembly I/O-এর জন্য WASI নামে একটি ডি-ফ্যাক্টো স্ট্যান্ডার্ড রয়েছে। এটি সিস্টেম ভাষার জন্য একটি সংকলন লক্ষ্য হিসাবে ডিজাইন করা হয়েছিল, এবং এটি একটি ঐতিহ্যগত সিঙ্ক্রোনাস আকারে সমস্ত ধরণের ফাইল সিস্টেম এবং অন্যান্য ক্রিয়াকলাপকে প্রকাশ করে।

যদি আপনি একটি অন্য মানচিত্র করতে পারে? তারপর আপনি WASI টার্গেটকে সমর্থনকারী যেকোন টুলচেনের সাহায্যে যেকোন সোর্স ল্যাঙ্গুয়েজে যেকোন অ্যাপ্লিকেশন কম্পাইল করতে পারেন এবং এটিকে ওয়েবে একটি স্যান্ডবক্সে চালাতে পারেন, যদিও এটিকে বাস্তব ব্যবহারকারী ফাইলে কাজ করার অনুমতি দেয়! Asyncify দিয়ে, আপনি ঠিক এটি করতে পারেন।

এই ডেমোতে, আমি WASI-তে কয়েকটি ছোট প্যাচ সহ রাস্ট কোরিউটিল ক্রেট সংকলন করেছি, যা Asyncify ট্রান্সফর্মের মাধ্যমে পাস করেছি এবং জাভাস্ক্রিপ্টের পাশে WASI থেকে ফাইল সিস্টেম অ্যাক্সেস API-তে অ্যাসিঙ্ক্রোনাস বাইন্ডিং প্রয়োগ করেছি। একবার Xterm.js টার্মিনাল কম্পোনেন্টের সাথে মিলিত হলে, এটি ব্রাউজার ট্যাবে চলমান একটি বাস্তবসম্মত শেল প্রদান করে এবং বাস্তব ব্যবহারকারী ফাইলগুলিতে অপারেটিং করে - ঠিক একটি আসল টার্মিনালের মতো।

এটি https://wasi.rreverser.com/ এ লাইভ দেখুন।

Asyncify ব্যবহার-ক্ষেত্রগুলি শুধুমাত্র টাইমার এবং ফাইল সিস্টেমের মধ্যে সীমাবদ্ধ নয়। আপনি আরও যেতে পারেন এবং ওয়েবে আরও কুলুঙ্গি API ব্যবহার করতে পারেন।

উদাহরণস্বরূপ, Asyncify-এর সাহায্যে, একটি WebUSB API- তে libusb — সম্ভবত USB ডিভাইসগুলির সাথে কাজ করার জন্য সবচেয়ে জনপ্রিয় নেটিভ লাইব্রেরি — ম্যাপ করা সম্ভব, যা ওয়েবে এই জাতীয় ডিভাইসগুলিতে অ্যাসিঙ্ক্রোনাস অ্যাক্সেস দেয়৷ একবার ম্যাপ করা এবং কম্পাইল করা হলে, আমি একটি ওয়েব পৃষ্ঠার স্যান্ডবক্সে সরাসরি নির্বাচিত ডিভাইসগুলির বিরুদ্ধে চালানোর জন্য আদর্শ libusb পরীক্ষা এবং উদাহরণ পেয়েছি।

একটি ওয়েব পৃষ্ঠায় libusb ডিবাগ আউটপুটের স্ক্রিনশট, সংযুক্ত ক্যানন ক্যামেরা সম্পর্কে তথ্য দেখাচ্ছে

এটি সম্ভবত অন্য ব্লগ পোস্টের জন্য একটি গল্প যদিও.

এই উদাহরণগুলি দেখায় যে Asyncify ব্যবধান পূরণ করতে এবং ওয়েবে সমস্ত ধরণের অ্যাপ্লিকেশন পোর্ট করার জন্য কতটা শক্তিশালী হতে পারে, আপনাকে কার্যকারিতা হারানো ছাড়াই ক্রস-প্ল্যাটফর্ম অ্যাক্সেস, স্যান্ডবক্সিং এবং আরও ভাল নিরাপত্তা লাভ করতে দেয়।

,

ওয়েবে I/O APIগুলি অ্যাসিঙ্ক্রোনাস, কিন্তু বেশিরভাগ সিস্টেম ভাষায় তারা সিঙ্ক্রোনাস। WebAssembly-এ কোড কম্পাইল করার সময়, আপনাকে এক ধরনের API-এর সাথে আরেকটা ব্রিজ করতে হবে—এবং এই ব্রিজটি হল Asyncify। এই পোস্টে, আপনি শিখবেন কখন এবং কীভাবে Asyncify ব্যবহার করবেন এবং কীভাবে এটি হুডের অধীনে কাজ করে।

সিস্টেমের ভাষায় I/O

আমি সি-তে একটি সাধারণ উদাহরণ দিয়ে শুরু করব। বলুন, আপনি একটি ফাইল থেকে ব্যবহারকারীর নাম পড়তে চান এবং "হ্যালো, (ব্যবহারকারীর নাম)!" দিয়ে তাদের শুভেচ্ছা জানান। বার্তা:

#include <stdio.h>

int main() {
    FILE *stream = fopen("name.txt", "r");
    char name[20+1];
    size_t len = fread(&name, 1, 20, stream);
    name[len] = '\0';
    fclose(stream);
    printf("Hello, %s!\n", name);
    return 0;
}

যদিও উদাহরণটি খুব বেশি কিছু করে না, এটি ইতিমধ্যেই এমন কিছু প্রদর্শন করে যা আপনি যে কোনও আকারের অ্যাপ্লিকেশনে পাবেন: এটি বাহ্যিক বিশ্বের কিছু ইনপুট পড়ে, অভ্যন্তরীণভাবে সেগুলিকে প্রক্রিয়া করে এবং আউটপুটগুলিকে বাহ্যিক বিশ্বে ফেরত পাঠায়। বাইরের বিশ্বের সাথে এই ধরনের সমস্ত মিথস্ক্রিয়া কিছু ফাংশনের মাধ্যমে ঘটে যাকে সাধারণত ইনপুট-আউটপুট ফাংশন বলা হয়, যাকে I/O তে সংক্ষিপ্ত করা হয়।

C থেকে নাম পড়ার জন্য, আপনার কমপক্ষে দুটি গুরুত্বপূর্ণ I/O কল দরকার: fopen , ফাইলটি খুলতে এবং এটি থেকে ডেটা পড়ার জন্য fread । একবার আপনি ডেটা পুনরুদ্ধার করার পরে, আপনি কনসোলে ফলাফল প্রিন্ট করতে অন্য I/O ফাংশন printf ব্যবহার করতে পারেন।

এই ফাংশনগুলি প্রথম নজরে বেশ সহজ দেখায় এবং ডেটা পড়তে বা লিখতে জড়িত যন্ত্রপাতি সম্পর্কে আপনাকে দুবার ভাবতে হবে না। যাইহোক, পরিবেশের উপর নির্ভর করে, ভিতরে অনেক কিছু ঘটতে পারে:

  • ইনপুট ফাইলটি স্থানীয় ড্রাইভে অবস্থিত থাকলে, ফাইলটি সনাক্ত করতে অ্যাপ্লিকেশনটিকে মেমরি এবং ডিস্ক অ্যাক্সেসের একটি সিরিজ সঞ্চালন করতে হবে, অনুমতি পরীক্ষা করতে হবে, পড়ার জন্য এটি খুলতে হবে এবং তারপরে অনুরোধকৃত সংখ্যক বাইট পুনরুদ্ধার না হওয়া পর্যন্ত ব্লক দ্বারা ব্লক পড়তে হবে। . আপনার ডিস্কের গতি এবং অনুরোধ করা আকারের উপর নির্ভর করে এটি বেশ ধীর হতে পারে।
  • অথবা, ইনপুট ফাইলটি একটি মাউন্ট করা নেটওয়ার্ক অবস্থানে অবস্থিত হতে পারে, এই ক্ষেত্রে, নেটওয়ার্ক স্ট্যাকটিও এখন জড়িত থাকবে, জটিলতা, বিলম্বিতা এবং প্রতিটি অপারেশনের জন্য সম্ভাব্য পুনঃপ্রচারের সংখ্যা বৃদ্ধি করবে।
  • অবশেষে, এমনকি printf জিনিসগুলি কনসোলে প্রিন্ট করার গ্যারান্টি দেওয়া হয় না এবং এটি একটি ফাইল বা নেটওয়ার্ক অবস্থানে পুনঃনির্দেশিত হতে পারে, এই ক্ষেত্রে এটিকে উপরের একই পদক্ষেপগুলি দিয়ে যেতে হবে।

দীর্ঘ গল্প সংক্ষেপে, I/O ধীর হতে পারে এবং আপনি কোডের দিকে এক নজরে একটি নির্দিষ্ট কল কতক্ষণ সময় নেবে তা অনুমান করতে পারবেন না। সেই অপারেশন চলাকালীন, আপনার পুরো অ্যাপ্লিকেশনটি হিমায়িত এবং ব্যবহারকারীর কাছে প্রতিক্রিয়াহীন দেখাবে৷

এটি C বা C++ এর মধ্যেও সীমাবদ্ধ নয়। বেশিরভাগ সিস্টেম ল্যাঙ্গুয়েজ সমস্ত I/O সিঙ্ক্রোনাস API-এর আকারে উপস্থাপন করে। উদাহরণ স্বরূপ, আপনি যদি উদাহরণটিকে Rust-এ অনুবাদ করেন, তাহলে APIটি সহজ দেখাতে পারে, কিন্তু একই নীতি প্রযোজ্য। আপনি শুধু একটি কল করুন এবং সিঙ্ক্রোনাসভাবে ফলাফলটি ফেরত দেওয়ার জন্য অপেক্ষা করুন, যখন এটি সমস্ত ব্যয়বহুল ক্রিয়াকলাপ সম্পাদন করে এবং অবশেষে একটি একক আহ্বানে ফলাফলটি ফেরত দেয়:

fn main() {
    let s = std::fs::read_to_string("name.txt");
    println!("Hello, {}!", s);
}

কিন্তু যখন আপনি WebAssembly-তে সেই নমুনাগুলির মধ্যে যেকোনও কম্পাইল করার চেষ্টা করেন এবং ওয়েবে অনুবাদ করেন তখন কী হয়? অথবা, একটি নির্দিষ্ট উদাহরণ প্রদান করতে, "ফাইল রিড" অপারেশন কি অনুবাদ করতে পারে? এটি কিছু স্টোরেজ থেকে ডেটা পড়তে হবে।

ওয়েবের অ্যাসিঙ্ক্রোনাস মডেল

ওয়েবে বিভিন্ন ধরণের স্টোরেজ বিকল্প রয়েছে যা আপনি ম্যাপ করতে পারেন, যেমন ইন-মেমরি স্টোরেজ (JS অবজেক্ট), localStorage , IndexedDB , সার্ভার-সাইড স্টোরেজ, এবং একটি নতুন ফাইল সিস্টেম অ্যাক্সেস API

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

এটি ওয়েবে কোড কার্যকর করার মূল বৈশিষ্ট্যগুলির মধ্যে একটি: যেকোন সময়-সাপেক্ষ ক্রিয়াকলাপ, যার মধ্যে যেকোন I/O রয়েছে, অসিঙ্ক্রোনাস হতে হবে।

কারণ হল ওয়েব ঐতিহাসিকভাবে একক-থ্রেডেড, এবং UI-কে স্পর্শ করে এমন যেকোনো ব্যবহারকারী কোডকে UI-এর মতো একই থ্রেডে চলতে হবে। এটি সিপিইউ সময়ের জন্য লেআউট, রেন্ডারিং এবং ইভেন্ট পরিচালনার মতো অন্যান্য গুরুত্বপূর্ণ কাজের সাথে প্রতিযোগিতা করতে হবে। আপনি জাভাস্ক্রিপ্ট বা WebAssembly-এর একটি টুকরা একটি "ফাইল রিড" অপারেশন শুরু করতে সক্ষম হতে চান না এবং অন্য সব কিছুকে ব্লক করতে পারেন—সম্পূর্ণ ট্যাব, বা, অতীতে, সম্পূর্ণ ব্রাউজার—মিলিসেকেন্ড থেকে কয়েক সেকেন্ডের জন্য , এটা শেষ না হওয়া পর্যন্ত।

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

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

এই প্রক্রিয়া সম্পর্কে মনে রাখা গুরুত্বপূর্ণ বিষয় হল যে, আপনার কাস্টম জাভাস্ক্রিপ্ট (বা WebAssembly) কোড কার্যকর করার সময়, ইভেন্ট লুপটি ব্লক করা হয় এবং এটি থাকাকালীন, কোনও বহিরাগত হ্যান্ডলার, ইভেন্ট, I/O-তে প্রতিক্রিয়া জানানোর কোনও উপায় নেই। ইত্যাদি। I/O ফলাফলগুলি ফিরে পাওয়ার একমাত্র উপায় হল একটি কলব্যাক নিবন্ধন করা, আপনার কোড কার্যকর করা শেষ করা এবং ব্রাউজারকে নিয়ন্ত্রণ ফিরিয়ে দেওয়া যাতে এটি কোনো মুলতুবি থাকা কাজগুলিকে প্রক্রিয়াজাত করতে পারে। একবার I/O শেষ হয়ে গেলে, আপনার হ্যান্ডলার সেই কাজগুলির মধ্যে একটি হয়ে উঠবে এবং কার্যকর করা হবে।

উদাহরণস্বরূপ, যদি আপনি আধুনিক জাভাস্ক্রিপ্টে উপরের নমুনাগুলি পুনরায় লিখতে চান এবং একটি দূরবর্তী URL থেকে একটি নাম পড়ার সিদ্ধান্ত নেন, তাহলে আপনি Fetch API এবং async-await সিনট্যাক্স ব্যবহার করবেন:

async function main() {
  let response = await fetch("name.txt");
  let name = await response.text();
  console.log("Hello, %s!", name);
}

যদিও এটি সিঙ্ক্রোনাস দেখায়, হুডের নীচে প্রতিটি await মূলত কলব্যাকের জন্য সিনট্যাক্স চিনি:

function main() {
  return fetch("name.txt")
    .then(response => response.text())
    .then(name => console.log("Hello, %s!", name));
}

এই ডি-সুগারড উদাহরণে, যা একটু পরিষ্কার, একটি অনুরোধ শুরু হয়েছে এবং প্রতিক্রিয়াগুলি প্রথম কলব্যাকের সাথে সদস্যতা নেওয়া হয়েছে৷ একবার ব্রাউজার প্রাথমিক প্রতিক্রিয়া-শুধুমাত্র HTTP শিরোনামগুলি পায়-এটি অ্যাসিঙ্ক্রোনাসভাবে এই কলব্যাককে আহ্বান করে। কলব্যাক response.text() ব্যবহার করে টেক্সট হিসেবে বডি পড়া শুরু করে এবং অন্য কলব্যাকের সাথে ফলাফলে সদস্যতা নেয়। অবশেষে, একবার fetch সমস্ত বিষয়বস্তু পুনরুদ্ধার করা হলে, এটি শেষ কলব্যাককে আহ্বান করে, যা "হ্যালো, (ব্যবহারকারীর নাম)!" কনসোলে

এই পদক্ষেপগুলির অ্যাসিঙ্ক্রোনাস প্রকৃতির জন্য ধন্যবাদ, মূল ফাংশনটি I/O নির্ধারিত হওয়ার সাথে সাথে ব্রাউজারে নিয়ন্ত্রণ ফিরিয়ে দিতে পারে এবং সম্পূর্ণ UI রেসপন্সিভ ছেড়ে দিতে পারে এবং রেন্ডারিং, স্ক্রলিং ইত্যাদি সহ অন্যান্য কাজের জন্য উপলব্ধ থাকতে পারে। I/O ব্যাকগ্রাউন্ডে কার্যকর হচ্ছে।

একটি চূড়ান্ত উদাহরণ হিসাবে, এমনকি "স্লিপ" এর মতো সাধারণ API, যা একটি অ্যাপ্লিকেশনকে একটি নির্দিষ্ট সংখ্যক সেকেন্ড অপেক্ষা করে, এটিও একটি I/O অপারেশনের একটি রূপ:

#include <stdio.h>
#include <unistd.h>
// ...
printf("A\n");
sleep(1);
printf("B\n");

অবশ্যই, আপনি এটিকে খুব সহজবোধ্যভাবে অনুবাদ করতে পারেন যা সময়ের মেয়াদ শেষ না হওয়া পর্যন্ত বর্তমান থ্রেডটিকে ব্লক করবে:

console.log("A");
for (let start = Date.now(); Date.now() - start < 1000;);
console.log("B");

প্রকৃতপক্ষে, এমস্ক্রিপ্টেন "স্লিপ" এর ডিফল্ট বাস্তবায়নে ঠিক এটিই করে, তবে এটি খুব অকার্যকর, পুরো UI ব্লক করবে এবং এর মধ্যে অন্য কোনও ইভেন্ট পরিচালনা করার অনুমতি দেবে না। সাধারণত, উত্পাদন কোডে এটি করবেন না।

পরিবর্তে, জাভাস্ক্রিপ্টে "sleep"-এর আরও একটি মূর্খ সংস্করণ setTimeout() কল করা এবং হ্যান্ডলারের সাথে সদস্যতা নেওয়া জড়িত:

console.log("A");
setTimeout(() => {
    console.log("B");
}, 1000);

এই সব উদাহরণ এবং এপিআই সাধারণ কি? প্রতিটি ক্ষেত্রে, মূল সিস্টেমের ভাষায় বাহাদুরি কোড I/O-এর জন্য একটি ব্লকিং API ব্যবহার করে, যেখানে ওয়েবের জন্য একটি সমতুল্য উদাহরণ পরিবর্তে একটি অ্যাসিঙ্ক্রোনাস API ব্যবহার করে। ওয়েবে কম্পাইল করার সময়, আপনাকে সেই দুটি এক্সিকিউশন মডেলের মধ্যে কোনো না কোনোভাবে রূপান্তর করতে হবে এবং WebAssembly-এর এখনও তা করার কোনো অন্তর্নির্মিত ক্ষমতা নেই।

Asyncify দিয়ে ব্যবধান পূরণ করা

এখানেই Asyncify আসে। Asyncify হল Emscripten দ্বারা সমর্থিত একটি কম্পাইল-টাইম বৈশিষ্ট্য যা সম্পূর্ণ প্রোগ্রামকে বিরাম দিতে এবং পরে অ্যাসিঙ্ক্রোনাসভাবে পুনরায় শুরু করতে দেয়।

একটি জাভাস্ক্রিপ্ট -> WebAssembly -> web API -> async টাস্ক ইনভোকেশন বর্ণনা করে একটি কল গ্রাফ, যেখানে Asyncify অ্যাসিঙ্ক টাস্কের ফলাফলকে আবার WebAssembly-এ সংযুক্ত করে

Emscripten-এর সাথে C/C++-এ ব্যবহার

আপনি যদি শেষ উদাহরণের জন্য একটি অ্যাসিঙ্ক্রোনাস স্লিপ বাস্তবায়ন করতে Asyncify ব্যবহার করতে চান তবে আপনি এটি এভাবে করতে পারেন:

#include <stdio.h>
#include <emscripten.h>

EM_JS(void, async_sleep, (int seconds), {
    Asyncify.handleSleep(wakeUp => {
        setTimeout(wakeUp, seconds * 1000);
    });
});
…
puts("A");
async_sleep(1);
puts("B");

EM_JS হল একটি ম্যাক্রো যা জাভাস্ক্রিপ্টের স্নিপেটগুলিকে C ফাংশন হিসাবে সংজ্ঞায়িত করতে দেয়৷ ভিতরে, একটি ফাংশন Asyncify.handleSleep() ব্যবহার করুন যা Emscripten কে প্রোগ্রাম স্থগিত করতে বলে এবং একটি wakeUp() হ্যান্ডলার প্রদান করে যা অ্যাসিঙ্ক্রোনাস অপারেশন শেষ হলে কল করা উচিত। উপরের উদাহরণে, হ্যান্ডলারটিকে setTimeout() এ পাস করা হয়েছে, কিন্তু এটি অন্য কোনো প্রসঙ্গে ব্যবহার করা যেতে পারে যা কলব্যাক গ্রহণ করে। অবশেষে, আপনি যেকোন জায়গায় async_sleep() কল করতে পারেন ঠিক রেগুলার sleep() বা অন্য কোন সিঙ্ক্রোনাস API এর মত।

এই ধরনের কোড কম্পাইল করার সময়, আপনাকে Asyncify বৈশিষ্ট্যটি সক্রিয় করতে Emscripten কে বলতে হবে। -s ASYNCIFY পাশাপাশি -s ASYNCIFY_IMPORTS=[func1, func2] ফাংশনগুলির একটি অ্যারের মতো তালিকা যা অ্যাসিঙ্ক্রোনাস হতে পারে পাস করে এটি করুন।

emcc -O2 \
    -s ASYNCIFY \
    -s ASYNCIFY_IMPORTS=[async_sleep] \
    ...

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

এখন, যখন আপনি ব্রাউজারে এই কোডটি কার্যকর করবেন তখন আপনি একটি বিরামবিহীন আউটপুট লগ দেখতে পাবেন যেমনটি আপনি আশা করেছিলেন, A এর পরে অল্প বিলম্বের পরে B আসবে।

A
B

আপনি Asyncify ফাংশন থেকেও মান ফেরত দিতে পারেন। আপনাকে যা করতে হবে তা হল handleSleep() এর ফলাফলটি ফেরত দিন এবং ফলাফলটি wakeUp() কলব্যাকে পাস করুন। উদাহরণস্বরূপ, যদি, একটি ফাইল থেকে পড়ার পরিবর্তে, আপনি একটি দূরবর্তী সংস্থান থেকে একটি নম্বর আনতে চান, আপনি একটি অনুরোধ ইস্যু করতে, সি কোডটি স্থগিত করতে এবং প্রতিক্রিয়া বডি পুনরুদ্ধার করার পরে পুনরায় শুরু করতে নীচের একটির মতো একটি স্নিপেট ব্যবহার করতে পারেন —সবই নির্বিঘ্নে করা হয়েছে যেন কলটি সিঙ্ক্রোনাস।

EM_JS(int, get_answer, (), {
     return Asyncify.handleSleep(wakeUp => {
        fetch("answer.txt")
            .then(response => response.text())
            .then(text => wakeUp(Number(text)));
    });
});
puts("Getting answer...");
int answer = get_answer();
printf("Answer is %d\n", answer);

প্রকৃতপক্ষে, fetch() এর মত প্রতিশ্রুতি-ভিত্তিক APIগুলির জন্য, আপনি কলব্যাক-ভিত্তিক API ব্যবহার করার পরিবর্তে JavaScript-এর async-await বৈশিষ্ট্যের সাথে Asyncify-কে একত্রিত করতে পারেন। তার জন্য Asyncify.handleSleep() এর পরিবর্তে Asyncify.handleAsync() কল করুন। তারপরে, একটি wakeUp() কলব্যাকের সময়সূচী করার পরিবর্তে, আপনি একটি async JavaScript ফাংশন পাস করতে পারেন এবং ভিতরে await এবং return ব্যবহার করতে পারেন, কোডটিকে আরও বেশি স্বাভাবিক এবং সিঙ্ক্রোনাস দেখায়, যেখানে অ্যাসিঙ্ক্রোনাস I/O-এর কোনো সুবিধা হারাবেন না।

EM_JS(int, get_answer, (), {
     return Asyncify.handleAsync(async () => {
        let response = await fetch("answer.txt");
        let text = await response.text();
        return Number(text);
    });
});

int answer = get_answer();

জটিল মান অপেক্ষা করছে

কিন্তু এই উদাহরণটি এখনও আপনাকে শুধুমাত্র সংখ্যার মধ্যে সীমাবদ্ধ করে। যদি আপনি মূল উদাহরণটি বাস্তবায়ন করতে চান, যেখানে আমি একটি স্ট্রিং হিসাবে একটি ফাইল থেকে ব্যবহারকারীর নাম পেতে চেষ্টা করেছি? আচ্ছা, আপনি এটাও করতে পারেন!

Emscripten এম্বিন্ড নামে একটি বৈশিষ্ট্য প্রদান করে যা আপনাকে জাভাস্ক্রিপ্ট এবং C++ মানগুলির মধ্যে রূপান্তরগুলি পরিচালনা করতে দেয়। এটি Asyncify-এর জন্যও সমর্থন রয়েছে, তাই আপনি বহিরাগত Promise s-এ await() কল করতে পারেন এবং এটি async-await JavaScript কোডের মতোই await করবে:

val fetch = val::global("fetch");
val response = fetch(std::string("answer.txt")).await();
val text = response.call<val>("text").await();
auto answer = text.as<std::string>();

এই পদ্ধতিটি ব্যবহার করার সময়, আপনাকে একটি কম্পাইল পতাকা হিসাবে ASYNCIFY_IMPORTS পাস করতে হবে না, কারণ এটি ইতিমধ্যেই ডিফল্টরূপে অন্তর্ভুক্ত রয়েছে৷

ঠিক আছে, তাই এই সব Emscripten মহান কাজ করে. অন্যান্য টুলচেইন এবং ভাষা সম্পর্কে কি?

অন্যান্য ভাষা থেকে ব্যবহার

বলুন যে আপনার মরিচা কোডের কোথাও আপনার একটি অনুরূপ সিঙ্ক্রোনাস কল রয়েছে যা আপনি ওয়েবে একটি অ্যাসিঙ্ক API এ ম্যাপ করতে চান৷ দেখা যাচ্ছে, আপনিও তা করতে পারেন!

প্রথমত, আপনাকে extern ব্লক (বা বিদেশী ফাংশনের জন্য আপনার নির্বাচিত ভাষার সিনট্যাক্স) মাধ্যমে একটি নিয়মিত আমদানি হিসাবে এই ধরনের ফাংশনকে সংজ্ঞায়িত করতে হবে।

extern {
    fn get_answer() -> i32;
}

println!("Getting answer...");
let answer = get_answer();
println!("Answer is {}", answer);

এবং আপনার কোড WebAssembly এ কম্পাইল করুন:

cargo build --target wasm32-unknown-unknown

এখন আপনাকে স্ট্যাক সংরক্ষণ/পুনরুদ্ধার করার জন্য কোড সহ WebAssembly ফাইলটিকে ইনস্ট্রুমেন্ট করতে হবে। C/C++ এর জন্য, Emscripten আমাদের জন্য এটি করবে, কিন্তু এটি এখানে ব্যবহার করা হয়নি, তাই প্রক্রিয়াটি একটু বেশি ম্যানুয়াল।

ভাগ্যক্রমে, Asyncify রূপান্তর নিজেই সম্পূর্ণরূপে টুলচেন-অজ্ঞেয়বাদী। এটি নির্বিচারে WebAssembly ফাইলগুলিকে রূপান্তর করতে পারে, এটি যে কম্পাইলার দ্বারা উত্পাদিত হোক না কেন। Binaryen টুলচেইন থেকে wasm-opt optimizer-এর অংশ হিসেবে ট্রান্সফর্মটি আলাদাভাবে প্রদান করা হয়েছে এবং এটিকে এভাবে আহ্বান করা যেতে পারে:

wasm-opt -O2 --asyncify \
      --pass-arg=asyncify-imports@env.get_answer \
      [...]

ট্রান্সফর্ম সক্রিয় করতে --asyncify পাস করুন, এবং তারপর অসিঙ্ক্রোনাস ফাংশনগুলির একটি কমা-বিভাজিত তালিকা প্রদান করতে --pass-arg=… ব্যবহার করুন, যেখানে প্রোগ্রামের অবস্থা স্থগিত করা উচিত এবং পরে পুনরায় চালু করা উচিত।

যা বাকি আছে তা হল সমর্থনকারী রানটাইম কোড প্রদান করা যা আসলে তা করবে—ওয়েবঅ্যাসেম্বলি কোড সাসপেন্ড এবং পুনরায় শুরু করুন। আবার, C/C++ ক্ষেত্রে এটি Emscripten দ্বারা অন্তর্ভুক্ত করা হবে, কিন্তু এখন আপনার কাস্টম জাভাস্ক্রিপ্ট আঠালো কোড প্রয়োজন যা নির্বিচারে WebAssembly ফাইলগুলি পরিচালনা করবে। আমরা একটি লাইব্রেরি তৈরি করেছি শুধু যে জন্য.

আপনি এটিকে GitHub-এ https://github.com/GoogleChromeLabs/asyncify বা npm-এ asyncify-wasm নামে খুঁজে পেতে পারেন।

এটি একটি স্ট্যান্ডার্ড WebAssembly instantiation API অনুকরণ করে, কিন্তু তার নিজস্ব নামস্থানের অধীনে। শুধুমাত্র পার্থক্য হল, একটি নিয়মিত WebAssembly API-এর অধীনে আপনি শুধুমাত্র আমদানি হিসাবে সিঙ্ক্রোনাস ফাংশন প্রদান করতে পারেন, যখন Asyncify র‍্যাপারের অধীনে, আপনি অ্যাসিঙ্ক্রোনাস আমদানিও প্রদান করতে পারেন:

const { instance } = await Asyncify.instantiateStreaming(fetch('app.wasm'), {
    env: {
        async get_answer() {
            let response = await fetch("answer.txt");
            let text = await response.text();
            return Number(text);
        }
    }
});
…
await instance.exports.main();

একবার আপনি যেমন একটি অ্যাসিঙ্ক্রোনাস ফাংশন কল করার চেষ্টা করেন - যেমন উপরের উদাহরণে get_answer() - WebAssembly দিক থেকে, লাইব্রেরি ফিরে আসা Promise সনাক্ত করবে, WebAssembly অ্যাপ্লিকেশনটির অবস্থা স্থগিত করবে এবং সংরক্ষণ করবে, প্রতিশ্রুতি সম্পন্ন হওয়ার জন্য সদস্যতা গ্রহণ করবে এবং পরে , একবার এটি সমাধান হয়ে গেলে, নির্বিঘ্নে কল স্ট্যাক এবং স্টেট পুনরুদ্ধার করুন এবং এক্সিকিউশন চালিয়ে যান যেন কিছুই হয়নি।

যেহেতু মডিউলের যেকোন ফাংশন একটি অ্যাসিঙ্ক্রোনাস কল করতে পারে, সমস্ত রপ্তানিও সম্ভাব্য অ্যাসিঙ্ক্রোনাস হয়ে যায়, তাই সেগুলিও মোড়ানো হয়। আপনি উপরের উদাহরণে লক্ষ্য করেছেন যে আপনার instance.exports.main() ফলাফলটি await করতে হবে ex

এই সমস্ত হুডের নীচে কীভাবে কাজ করে?

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

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

পূর্ববর্তী প্রদর্শিত অ্যাসিঙ্ক্রোনাস ঘুমের উদাহরণ সংকলন করার সময়:

puts("A");
async_sleep(1);
puts("B");

অ্যাসিনসিফাই এই কোডটি নেয় এবং এটিকে মোটামুটিভাবে নিম্নলিখিতটির মতো রূপান্তর করে (সিউডো-কোড, বাস্তব রূপান্তর এর চেয়ে বেশি জড়িত):

if (mode == NORMAL_EXECUTION) {
    puts("A");
    async_sleep(1);
    saveLocals();
    mode = UNWINDING;
    return;
}
if (mode == REWINDING) {
    restoreLocals();
    mode = NORMAL_EXECUTION;
}
puts("B");

প্রাথমিকভাবে mode NORMAL_EXECUTION সেট করা হয়। অনুরূপভাবে, এই জাতীয় রূপান্তরিত কোডটি প্রথমবার কার্যকর করা হয়, কেবলমাত্র async_sleep() পর্যন্ত নেতৃত্বাধীন অংশটি মূল্যায়ন করা হবে। অ্যাসিঙ্ক্রোনাস অপারেশন নির্ধারিত হওয়ার সাথে সাথেই অ্যাসিঙ্কাইফাই সমস্ত স্থানীয়কে বাঁচায় এবং প্রতিটি ফাংশন থেকে শীর্ষে সমস্ত উপায়ে ফিরে স্ট্যাকটি উন্মুক্ত করে, এইভাবে ব্রাউজার ইভেন্ট লুপকে নিয়ন্ত্রণ ফিরিয়ে দেয়।

তারপরে, একবার async_sleep() সমাধান হয়ে গেলে, অ্যাসিনসিফাই সাপোর্ট কোডটি REWINDING mode পরিবর্তন করবে এবং ফাংশনটিকে আবার কল করবে। এবার, "সাধারণ এক্সিকিউশন" শাখাটি এড়িয়ে গেছে - যেহেতু এটি ইতিমধ্যে গতবারের কাজটি করেছে এবং আমি "এ" মুদ্রণ এড়াতে চাই - এবং পরিবর্তে এটি সরাসরি "রিওয়াইন্ডিং" শাখায় আসে। একবার এটি পৌঁছে গেলে, এটি সমস্ত সঞ্চিত স্থানীয়দের পুনরুদ্ধার করে, মোডকে "স্বাভাবিক" এ পরিবর্তন করে এবং সম্পাদন চালিয়ে যায় যেন কোডটি কখনই প্রথম স্থানে থামানো হয় না।

রূপান্তর ব্যয়

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

একটি গ্রাফ বিভিন্ন মানদণ্ডের জন্য কোড আকারের ওভারহেড দেখায়, প্রায়-0% থেকে সূক্ষ্ম সুরযুক্ত অবস্থার অধীনে সবচেয়ে খারাপ ক্ষেত্রে 100% এরও বেশি

এটি আদর্শ নয়, তবে অনেক ক্ষেত্রে গ্রহণযোগ্য যখন বিকল্পটি পুরোপুরি কার্যকারিতা না করে বা মূল কোডটিতে উল্লেখযোগ্য পুনর্লিখন করতে পারে।

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

রিয়েল-ওয়ার্ল্ড ডেমো

এখন যেহেতু আপনি সহজ উদাহরণগুলি দেখেছেন, আমি আরও জটিল পরিস্থিতিতে এগিয়ে যাব।

নিবন্ধের শুরুতে যেমন উল্লেখ করা হয়েছে, ওয়েবে স্টোরেজ বিকল্পগুলির মধ্যে একটি হ'ল অ্যাসিনক্রোনাস ফাইল সিস্টেম অ্যাক্সেস এপিআই । এটি একটি ওয়েব অ্যাপ্লিকেশন থেকে একটি বাস্তব হোস্ট ফাইল সিস্টেমে অ্যাক্সেস সরবরাহ করে।

অন্যদিকে, কনসোল এবং সার্ভার-সাইডে ওয়েবসেম্বলি আই/ও এর জন্য ওয়াসি নামে একটি ডি-ফ্যাক্টো স্ট্যান্ডার্ড রয়েছে। এটি সিস্টেম ভাষাগুলির জন্য সংকলন লক্ষ্য হিসাবে ডিজাইন করা হয়েছিল এবং সমস্ত ধরণের ফাইল সিস্টেম এবং অন্যান্য ক্রিয়াকলাপগুলি একটি traditional তিহ্যবাহী সিঙ্ক্রোনাস আকারে প্রকাশ করে।

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

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

এটি https://wasi.rreverser.com/ এ সরাসরি দেখুন।

অ্যাসিঙ্কাইফাই ব্যবহার-কেসগুলি কেবল টাইমার এবং ফাইল সিস্টেমগুলিতে সীমাবদ্ধ নয়। আপনি আরও যেতে পারেন এবং ওয়েবে আরও কুলুঙ্গি এপিআই ব্যবহার করতে পারেন।

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

সংযুক্ত ক্যানন ক্যামেরা সম্পর্কে তথ্য দেখিয়ে একটি ওয়েব পৃষ্ঠায় লিবাসবি ডিবাগ আউটপুটের স্ক্রিনশট

যদিও এটি সম্ভবত অন্য একটি ব্লগ পোস্টের জন্য একটি গল্প।

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

,

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

আই/ও সিস্টেম ভাষায়

আমি সি -তে একটি সাধারণ উদাহরণ দিয়ে শুরু করব, আপনি কোনও ফাইল থেকে ব্যবহারকারীর নামটি পড়তে চান এবং তাদের "হ্যালো, (ব্যবহারকারীর নাম)" দিয়ে তাদের শুভেচ্ছা জানাতে চান! বার্তা:

#include <stdio.h>

int main() {
    FILE *stream = fopen("name.txt", "r");
    char name[20+1];
    size_t len = fread(&name, 1, 20, stream);
    name[len] = '\0';
    fclose(stream);
    printf("Hello, %s!\n", name);
    return 0;
}

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

সি থেকে নামটি পড়তে, আপনার ফাইলটি খুলতে কমপক্ষে দুটি গুরুত্বপূর্ণ আই/ও কল: fopen , এবং এটি থেকে ডেটা পড়ার জন্য fread প্রয়োজন। একবার আপনি ডেটা পুনরুদ্ধার করার পরে, আপনি ফলাফলটি কনসোলে মুদ্রণ করতে অন্য আই/ও ফাংশন printf ব্যবহার করতে পারেন।

এই ফাংশনগুলি প্রথম নজরে বেশ সহজ দেখায় এবং ডেটা পড়তে বা লেখার জন্য জড়িত যন্ত্রপাতি সম্পর্কে আপনাকে দু'বার ভাবতে হবে না। যাইহোক, পরিবেশের উপর নির্ভর করে ভিতরে অনেক কিছু চলছে:

  • যদি ইনপুট ফাইলটি কোনও স্থানীয় ড্রাইভে অবস্থিত থাকে তবে অ্যাপ্লিকেশনটিকে ফাইলটি সনাক্ত করতে, অনুমতিগুলি পরীক্ষা করতে, পড়ার জন্য এটি খুলতে এবং তারপরে ব্লক দ্বারা পড়তে হবে যতক্ষণ না বাইটের অনুরোধকৃত সংখ্যা পুনরুদ্ধার না করা হয় তার একটি সিরিজ মেমরি এবং ডিস্ক অ্যাক্সেস করতে হবে . আপনার ডিস্কের গতি এবং অনুরোধ করা আকারের উপর নির্ভর করে এটি বেশ ধীর হতে পারে।
  • অথবা, ইনপুট ফাইলটি একটি মাউন্ট করা নেটওয়ার্ক অবস্থানে অবস্থিত হতে পারে, এই ক্ষেত্রে, নেটওয়ার্ক স্ট্যাকটি এখনও জড়িত থাকবে, প্রতিটি ক্রিয়াকলাপের জন্য জটিলতা, বিলম্ব এবং সম্ভাব্য পুনর্বিন্যাসের সংখ্যা বাড়িয়ে তুলবে।
  • অবশেষে, এমনকি printf কনসোলে জিনিস মুদ্রণের গ্যারান্টিযুক্ত নয় এবং এটি কোনও ফাইল বা নেটওয়ার্কের স্থানে পুনঃনির্দেশিত হতে পারে, সেক্ষেত্রে এটি উপরের একই পদক্ষেপের মধ্য দিয়ে যেতে হবে।

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

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

fn main() {
    let s = std::fs::read_to_string("name.txt");
    println!("Hello, {}!", s);
}

তবে আপনি যখন এই নমুনাগুলির কোনওটি ওয়েবসেম্ব্লে সংকলন করার চেষ্টা করেন এবং সেগুলি ওয়েবে অনুবাদ করার চেষ্টা করেন তখন কী ঘটে? বা, একটি নির্দিষ্ট উদাহরণ সরবরাহ করতে, "ফাইল রিড" অপারেশনটিতে অনুবাদ করতে পারে কী? এটি কিছু স্টোরেজ থেকে ডেটা পড়তে হবে।

ওয়েবের অ্যাসিঙ্ক্রোনাস মডেল

ওয়েবে বিভিন্ন ধরণের স্টোরেজ বিকল্প রয়েছে যা আপনি মানচিত্র করতে পারেন, যেমন ইন-মেমরি স্টোরেজ (জেএস অবজেক্টস), localStorage , ইনডেক্সডডিবি , সার্ভার-সাইড স্টোরেজ এবং একটি নতুন ফাইল সিস্টেম অ্যাক্সেস এপিআই।

যাইহোক, এই এপিআইগুলির মধ্যে কেবল দুটি-ইন-মেমরি স্টোরেজ এবং localStorage -সিঙ্ক্রোনালি ব্যবহার করা যেতে পারে এবং উভয়ই আপনি যা সঞ্চয় করতে পারেন এবং কতক্ষণের জন্য সবচেয়ে সীমাবদ্ধ বিকল্প। অন্যান্য সমস্ত বিকল্পগুলি কেবল অ্যাসিনক্রোনাস এপিআই সরবরাহ করে।

এটি ওয়েবে কোড কার্যকর করার মূল বৈশিষ্ট্যগুলির মধ্যে একটি: যে কোনও সময় সাপেক্ষ অপারেশন, যার মধ্যে কোনও আই/ও অন্তর্ভুক্ত রয়েছে, অ্যাসিনক্রোনাস হতে হবে।

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

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

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

এই প্রক্রিয়াটি সম্পর্কে মনে রাখার গুরুত্বপূর্ণ বিষয়টি হ'ল, আপনার কাস্টম জাভাস্ক্রিপ্ট (বা ওয়েবসেমি) কোডটি কার্যকর করার সময়, ইভেন্ট লুপটি অবরুদ্ধ করা হয়েছে এবং যদিও এটি হয়, কোনও বাহ্যিক হ্যান্ডলার, ইভেন্ট, আই/ও, এর প্রতিক্রিয়া জানানোর কোনও উপায় নেই ইত্যাদি। আই/ও ফলাফলগুলি ফিরে পাওয়ার একমাত্র উপায় হ'ল একটি কলব্যাক নিবন্ধন করা, আপনার কোডটি কার্যকর করা শেষ করা এবং ব্রাউজারকে নিয়ন্ত্রণটি ফিরিয়ে দেওয়া যাতে এটি কোনও মুলতুবি কার্যগুলি প্রক্রিয়াজাতকরণ চালিয়ে যেতে পারে। আমি/ও শেষ হয়ে গেলে, আপনার হ্যান্ডলার সেই কাজগুলির মধ্যে একটি হয়ে যাবে এবং কার্যকর করা হবে।

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

async function main() {
  let response = await fetch("name.txt");
  let name = await response.text();
  console.log("Hello, %s!", name);
}

যদিও এটি সিঙ্ক্রোনাস দেখায়, হুডের নীচে প্রতিটি await মূলত কলব্যাকগুলির জন্য সিনট্যাক্স চিনি:

function main() {
  return fetch("name.txt")
    .then(response => response.text())
    .then(name => console.log("Hello, %s!", name));
}

এই ডি-স্যুগার্ড উদাহরণে, যা কিছুটা পরিষ্কার, একটি অনুরোধ শুরু করা হয় এবং প্রতিক্রিয়াগুলি প্রথম কলব্যাকের সাথে সাবস্ক্রাইব করা হয়। ব্রাউজারটি একবার প্রাথমিক প্রতিক্রিয়া গ্রহণ করে - কেবলমাত্র এইচটিটিপি শিরোনামগুলি - এটি অসাধারণভাবে এই কলব্যাকটি আহ্বান করে। কলব্যাকটি response.text() ব্যবহার করে বডিটি পাঠ্য হিসাবে পড়া শুরু করে এবং অন্য কলব্যাকের সাথে ফলাফলের সাবস্ক্রাইব করে। অবশেষে, একবার fetch সমস্ত বিষয়বস্তু পুনরুদ্ধার করে ফেললে, এটি সর্বশেষ কলব্যাকটি অনুরোধ করে, যা "হ্যালো, (ব্যবহারকারীর নাম)!" প্রিন্ট করে! কনসোলে

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

চূড়ান্ত উদাহরণ হিসাবে, এমনকি "স্লিপ" এর মতো সাধারণ এপিআইগুলি, যা কোনও অ্যাপ্লিকেশনকে নির্দিষ্ট কয়েক সেকেন্ডের জন্য অপেক্ষা করে তোলে, এটিও আই/ও অপারেশনের একটি ফর্ম:

#include <stdio.h>
#include <unistd.h>
// ...
printf("A\n");
sleep(1);
printf("B\n");

অবশ্যই, আপনি এটি খুব সোজা পদ্ধতিতে অনুবাদ করতে পারেন যা সময়ের মেয়াদ শেষ না হওয়া পর্যন্ত বর্তমান থ্রেডটি অবরুদ্ধ করে দেয়:

console.log("A");
for (let start = Date.now(); Date.now() - start < 1000;);
console.log("B");

প্রকৃতপক্ষে, এমস্ক্রিপ্টেন তার "ঘুম" এর ডিফল্ট বাস্তবায়নে ঠিক এটিই করেন, তবে এটি অত্যন্ত অদক্ষ, পুরো ইউআইটিকে অবরুদ্ধ করবে এবং এরই মধ্যে অন্য কোনও ইভেন্ট পরিচালনা করতে দেবে না। সাধারণত, উত্পাদন কোডে এটি করবেন না।

পরিবর্তে, জাভাস্ক্রিপ্টে "স্লিপ" এর আরও আইডিয়োমেটিক সংস্করণে setTimeout() কল করা এবং একটি হ্যান্ডলারের সাথে সাবস্ক্রাইব করা জড়িত:

console.log("A");
setTimeout(() => {
    console.log("B");
}, 1000);

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

অ্যাসিঙ্কাইফাইয়ের সাথে ব্যবধানটি ব্রিজ করা

এখানেই অ্যাসিঙ্কাইফায় আসে As অ্যাসিনসিফাই হ'ল একটি সংকলন-সময় বৈশিষ্ট্য যা এমস্ক্রিপ্টেন দ্বারা সমর্থিত যা পুরো প্রোগ্রামটি বিরতি দেয় এবং পরে এটি পুনরায় পুনরায় শুরু করার অনুমতি দেয়।

একটি জাভাস্ক্রিপ্ট বর্ণনা করে এমন একটি কল গ্রাফ -> ওয়েবসেম্বলি -> ওয়েব এপিআই -> অ্যাসিঙ্ক টাস্ক ইনভোকেশন, যেখানে অ্যাসিঙ্কাইফাই অ্যাসিঙ্ক টাস্কের ফলাফলটিকে আবার ওয়েবসেম্বলিতে সংযুক্ত করে

এমস্ক্রিপ্টেন সহ সি / সি ++ এ ব্যবহার

আপনি যদি শেষ উদাহরণের জন্য অ্যাসিনক্রোনাস ঘুম বাস্তবায়নের জন্য অ্যাসিঙ্কাইফাই ব্যবহার করতে চান তবে আপনি এটি করতে পারেন:

#include <stdio.h>
#include <emscripten.h>

EM_JS(void, async_sleep, (int seconds), {
    Asyncify.handleSleep(wakeUp => {
        setTimeout(wakeUp, seconds * 1000);
    });
});
…
puts("A");
async_sleep(1);
puts("B");

EM_JS এমন একটি ম্যাক্রো যা জাভাস্ক্রিপ্ট স্নিপেটগুলি সংজ্ঞায়িত করার অনুমতি দেয় যেন তারা সি ফাংশন। ভিতরে, একটি ফাংশন ব্যবহার করুন Asyncify.handleSleep() যা এমএসক্রিপ্টেনকে প্রোগ্রামটি স্থগিত করতে বলে এবং একটি wakeUp() হ্যান্ডলার সরবরাহ করে যা অ্যাসিঙ্ক্রোনাস অপারেশন শেষ হয়ে গেলে বলা উচিত। উপরের উদাহরণে, হ্যান্ডলারটি setTimeout() এ পাস করা হয়েছে, তবে এটি অন্য কোনও প্রসঙ্গে ব্যবহার করা যেতে পারে যা কলব্যাকগুলি গ্রহণ করে। শেষ অবধি, আপনি নিয়মিত sleep() বা অন্য কোনও সিঙ্ক্রোনাস এপিআইয়ের মতো যে কোনও জায়গায় আপনি async_sleep() কল করতে পারেন।

এই জাতীয় কোডটি সংকলন করার সময়, আপনাকে অ্যাসিঙ্কাইফাই বৈশিষ্ট্যটি সক্রিয় করতে এমস্ক্রিপ্টেনকে বলতে হবে। অ্যাসিঙ্কাইফাই_আইএমপোর্টস = [ফানক 1, ফানক 2] এ্যাকিনক্রোনাস হতে পারে এমন একটি অ্যারে -জাতীয় তালিকা সহ -s ASYNCIFY পাশাপাশি -এস অ্যাসিঙ্কাইফাই_আইএমপোর্টস -s ASYNCIFY_IMPORTS=[func1, func2] পাস করে এটি করুন।

emcc -O2 \
    -s ASYNCIFY \
    -s ASYNCIFY_IMPORTS=[async_sleep] \
    ...

এটি এমস্ক্রিপ্টেনকে জানতে দেয় যে এই ফাংশনগুলিতে যে কোনও কলগুলির জন্য রাজ্য সংরক্ষণ এবং পুনরুদ্ধার প্রয়োজন হতে পারে, তাই সংকলক এই জাতীয় কলগুলির চারপাশে সহায়ক কোড ইনজেকশন দেবে।

এখন, আপনি যখন ব্রাউজারে এই কোডটি সম্পাদন করবেন তখন আপনি একটি বিরামবিহীন আউটপুট লগ দেখতে পাবেন যেমন আপনি প্রত্যাশা করেছিলেন, বি এর পরে এ সংক্ষিপ্ত বিলম্বের পরে এসেছে

A
B

আপনি অ্যাসিনসিফাই ফাংশনগুলি থেকে মানগুলিও ফিরিয়ে দিতে পারেন। আপনার যা করতে হবে তা হ'ল handleSleep() এবং ফলাফলটি wakeUp() কলব্যাকে পাস করা। উদাহরণস্বরূপ, যদি, কোনও ফাইল থেকে পড়ার পরিবর্তে, আপনি কোনও দূরবর্তী সংস্থান থেকে একটি নম্বর আনতে চান, আপনি অনুরোধ জারি করতে, সি কোড স্থগিত করতে এবং প্রতিক্রিয়া বডিটি পুনরুদ্ধার করার পরে পুনরায় শুরু করতে নীচের মতো একটি স্নিপেট ব্যবহার করতে পারেন - সমস্ত নির্বিঘ্নে কাজ করেছে যেন কলটি সিঙ্ক্রোনাস ছিল।

EM_JS(int, get_answer, (), {
     return Asyncify.handleSleep(wakeUp => {
        fetch("answer.txt")
            .then(response => response.text())
            .then(text => wakeUp(Number(text)));
    });
});
puts("Getting answer...");
int answer = get_answer();
printf("Answer is %d\n", answer);

প্রকৃতপক্ষে, fetch() এর মতো প্রতিশ্রুতি-ভিত্তিক এপিআইয়ের জন্য, আপনি এমনকি কলব্যাক-ভিত্তিক এপিআই ব্যবহার না করে জাভাস্ক্রিপ্টের অ্যাসিঙ্ক-অ্যাগ বৈশিষ্ট্যটির সাথে অ্যাসিনসিফাই করতে পারেন। তার জন্য, Asyncify.handleSleep() এর পরিবর্তে Asyncify.handleAsync() তারপরে, একটি wakeUp() কলব্যাকের সময়সূচি নির্ধারণের পরিবর্তে, আপনি একটি async জাভাস্ক্রিপ্ট ফাংশনটি পাস করতে পারেন এবং await এবং ভিতরে return পারেন, কোডকে আরও প্রাকৃতিক এবং সিঙ্ক্রোনাস দেখায়, যখন অ্যাসিঙ্ক্রোনাস আই/ও এর কোনও সুবিধা হারাতে না পারে।

EM_JS(int, get_answer, (), {
     return Asyncify.handleAsync(async () => {
        let response = await fetch("answer.txt");
        let text = await response.text();
        return Number(text);
    });
});

int answer = get_answer();

জটিল মানগুলির অপেক্ষায়

তবে এই উদাহরণটি এখনও আপনাকে কেবল সংখ্যার মধ্যে সীমাবদ্ধ করে। আপনি যদি আসল উদাহরণটি বাস্তবায়ন করতে চান তবে যেখানে আমি কোনও ফাইল থেকে কোনও স্ট্রিং হিসাবে কোনও ব্যবহারকারীর নাম পাওয়ার চেষ্টা করেছি? ঠিক আছে, আপনি এটিও করতে পারেন!

এমস্ক্রিপ্টেন এম্বেড নামে একটি বৈশিষ্ট্য সরবরাহ করে যা আপনাকে জাভাস্ক্রিপ্ট এবং সি ++ মানগুলির মধ্যে রূপান্তর পরিচালনা করতে দেয়। এটির পাশাপাশি অ্যাসিনসিফাইয়ের পক্ষে সমর্থনও রয়েছে, যাতে আপনি বাহ্যিক Promise await() কল করতে পারেন এবং এটি অ্যাসিঙ্ক-আওট জাভাস্ক্রিপ্ট কোডে await মতোই কাজ করবে:

val fetch = val::global("fetch");
val response = fetch(std::string("answer.txt")).await();
val text = response.call<val>("text").await();
auto answer = text.as<std::string>();

এই পদ্ধতিটি ব্যবহার করার সময়, আপনার এমনকি ASYNCIFY_IMPORTS একটি সংকলন পতাকা হিসাবে পাস করার দরকার নেই, কারণ এটি ইতিমধ্যে ডিফল্টরূপে অন্তর্ভুক্ত রয়েছে।

ঠিক আছে, সুতরাং এটি সমস্ত এমস্ক্রিপ্টনে দুর্দান্ত কাজ করে। অন্যান্য সরঞ্জামচেন এবং ভাষা সম্পর্কে কী?

অন্যান্য ভাষা থেকে ব্যবহার

বলুন যে আপনার মরিচা কোডের কোথাও আপনার অনুরূপ সিঙ্ক্রোনাস কল রয়েছে যা আপনি ওয়েবে একটি অ্যাসিঙ্ক এপিআইতে মানচিত্র করতে চান। দেখা যাচ্ছে, আপনি এটিও করতে পারেন!

প্রথমত, আপনাকে extern ব্লকের (বা বিদেশী ফাংশনগুলির জন্য আপনার নির্বাচিত ভাষার সিনট্যাক্স) মাধ্যমে নিয়মিত আমদানি হিসাবে এই জাতীয় ফাংশনটি সংজ্ঞায়িত করতে হবে।

extern {
    fn get_answer() -> i32;
}

println!("Getting answer...");
let answer = get_answer();
println!("Answer is {}", answer);

এবং আপনার কোডটি ওয়েবসেম্বেলে সংকলন করুন:

cargo build --target wasm32-unknown-unknown

এখন আপনাকে স্ট্যাকটি সংরক্ষণ/পুনরুদ্ধার করার জন্য কোড সহ ওয়েবসেমি ফাইলটি উপকরণ করতে হবে। সি / সি ++ এর জন্য, এমস্ক্রিপ্টেন আমাদের জন্য এটি করবে, তবে এটি এখানে ব্যবহৃত হয়নি, সুতরাং প্রক্রিয়াটি আরও কিছুটা ম্যানুয়াল।

ভাগ্যক্রমে, অ্যাসিঙ্কাইফাই রূপান্তর নিজেই সম্পূর্ণ সরঞ্জামচেইন-অজনোস্টিক। এটি কোন সংকলক দ্বারা উত্পাদিত হয়েছে তা নির্বিচারে ওয়েবসেমি ফাইলগুলি রূপান্তর করতে পারে। রূপান্তরটি বাইনারেন টুলচেইন থেকে wasm-opt অপ্টিমাইজারের অংশ হিসাবে পৃথকভাবে সরবরাহ করা হয় এবং এর মতো অনুরোধ করা যেতে পারে:

wasm-opt -O2 --asyncify \
      --pass-arg=asyncify-imports@env.get_answer \
      [...]

রূপান্তর সক্ষম করতে পাস --asyncify -এবং তারপরে অ্যাসিঙ্ক্রোনাস ফাংশনগুলির একটি কমা-বিচ্ছিন্ন তালিকা সরবরাহ করতে --pass-arg=… ব্যবহার করুন, যেখানে প্রোগ্রামের অবস্থা স্থগিত করা উচিত এবং পরে পুনরায় শুরু করা উচিত।

যা কিছু বাকি রয়েছে তা হ'ল সমর্থনকারী রানটাইম কোড সরবরাহ করা যা আসলে এটি করবে - ওয়েবসেম্বলি কোড পুনরায় শুরু করুন এবং পুনরায় শুরু করুন। আবার, সি / সি ++ ক্ষেত্রে এটি এমস্ক্রিপ্টেন দ্বারা অন্তর্ভুক্ত করা হবে, তবে এখন আপনার কাস্টম জাভাস্ক্রিপ্ট আঠালো কোড দরকার যা স্বেচ্ছাসেবী ওয়েবসেমি ফাইলগুলি পরিচালনা করবে। আমরা কেবল তার জন্য একটি গ্রন্থাগার তৈরি করেছি।

আপনি এটি https://github.com/googlechromelabs/asyncify বা npm এ asyncify-wasm নামে এনপিএম এ খুঁজে পেতে পারেন।

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

const { instance } = await Asyncify.instantiateStreaming(fetch('app.wasm'), {
    env: {
        async get_answer() {
            let response = await fetch("answer.txt");
            let text = await response.text();
            return Number(text);
        }
    }
});
…
await instance.exports.main();

একবার আপনি উপরের উদাহরণে get_answer() এর মতো - যেমন একটি অ্যাসিঙ্ক্রোনাস ফাংশন কল করার চেষ্টা করেন - ওয়েবসেম্বলি দিক থেকে, গ্রন্থাগারটি প্রত্যাবর্তিত Promise সনাক্ত করবে, স্থগিতাদেশ এবং ওয়েবসেম্বল অ্যাপ্লিকেশনটির অবস্থা সংরক্ষণ করবে, প্রতিশ্রুতি সমাপ্তিতে সাবস্ক্রাইব করবে এবং পরে সাবস্ক্রাইব করবে এটি একবার সমাধান হয়ে গেলে, নির্বিঘ্নে কল স্ট্যাক এবং রাষ্ট্রটি পুনরুদ্ধার করুন এবং কার্যকর করা চালিয়ে যান যেন কিছুই ঘটেনি।

যেহেতু মডিউলটিতে যে কোনও ফাংশন একটি অ্যাসিঙ্ক্রোনাস কল করতে পারে, তাই সমস্ত রফতানি সম্ভাব্যভাবে অ্যাসিনক্রোনাস হয়ে যায়, তাই সেগুলিও মোড়ানো হয়। আপনি উপরের উদাহরণে লক্ষ্য করেছেন যে আপনার instance.exports.main() ফলাফলটি await করতে হবে ex

এই সমস্ত হুডের নীচে কীভাবে কাজ করে?

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

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

পূর্ববর্তী প্রদর্শিত অ্যাসিঙ্ক্রোনাস ঘুমের উদাহরণ সংকলন করার সময়:

puts("A");
async_sleep(1);
puts("B");

অ্যাসিনসিফাই এই কোডটি নেয় এবং এটিকে মোটামুটিভাবে নিম্নলিখিতটির মতো রূপান্তর করে (সিউডো-কোড, বাস্তব রূপান্তর এর চেয়ে বেশি জড়িত):

if (mode == NORMAL_EXECUTION) {
    puts("A");
    async_sleep(1);
    saveLocals();
    mode = UNWINDING;
    return;
}
if (mode == REWINDING) {
    restoreLocals();
    mode = NORMAL_EXECUTION;
}
puts("B");

প্রাথমিকভাবে mode NORMAL_EXECUTION সেট করা হয়। অনুরূপভাবে, এই জাতীয় রূপান্তরিত কোডটি প্রথমবার কার্যকর করা হয়, কেবলমাত্র async_sleep() পর্যন্ত নেতৃত্বাধীন অংশটি মূল্যায়ন করা হবে। অ্যাসিঙ্ক্রোনাস অপারেশন নির্ধারিত হওয়ার সাথে সাথেই অ্যাসিঙ্কাইফাই সমস্ত স্থানীয়কে বাঁচায় এবং প্রতিটি ফাংশন থেকে শীর্ষে সমস্ত উপায়ে ফিরে স্ট্যাকটি উন্মুক্ত করে, এইভাবে ব্রাউজার ইভেন্ট লুপকে নিয়ন্ত্রণ ফিরিয়ে দেয়।

তারপরে, একবার async_sleep() সমাধান হয়ে গেলে, অ্যাসিনসিফাই সাপোর্ট কোডটি REWINDING mode পরিবর্তন করবে এবং ফাংশনটিকে আবার কল করবে। এবার, "সাধারণ এক্সিকিউশন" শাখাটি এড়িয়ে গেছে - যেহেতু এটি ইতিমধ্যে গতবারের কাজটি করেছে এবং আমি "এ" মুদ্রণ এড়াতে চাই - এবং পরিবর্তে এটি সরাসরি "রিওয়াইন্ডিং" শাখায় আসে। একবার এটি পৌঁছে গেলে, এটি সমস্ত সঞ্চিত স্থানীয়দের পুনরুদ্ধার করে, মোডকে "স্বাভাবিক" এ পরিবর্তন করে এবং সম্পাদন চালিয়ে যায় যেন কোডটি কখনই প্রথম স্থানে থামানো হয় না।

রূপান্তর ব্যয়

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

একটি গ্রাফ বিভিন্ন মানদণ্ডের জন্য কোড আকারের ওভারহেড দেখায়, প্রায়-0% থেকে সূক্ষ্ম সুরযুক্ত অবস্থার অধীনে সবচেয়ে খারাপ ক্ষেত্রে 100% এরও বেশি

এটি আদর্শ নয়, তবে অনেক ক্ষেত্রে গ্রহণযোগ্য যখন বিকল্পটি পুরোপুরি কার্যকারিতা না করে বা মূল কোডটিতে উল্লেখযোগ্য পুনর্লিখন করতে পারে।

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

রিয়েল-ওয়ার্ল্ড ডেমো

এখন যেহেতু আপনি সহজ উদাহরণগুলি দেখেছেন, আমি আরও জটিল পরিস্থিতিতে এগিয়ে যাব।

নিবন্ধের শুরুতে যেমন উল্লেখ করা হয়েছে, ওয়েবে স্টোরেজ বিকল্পগুলির মধ্যে একটি হ'ল অ্যাসিনক্রোনাস ফাইল সিস্টেম অ্যাক্সেস এপিআই । এটি একটি ওয়েব অ্যাপ্লিকেশন থেকে একটি বাস্তব হোস্ট ফাইল সিস্টেমে অ্যাক্সেস সরবরাহ করে।

অন্যদিকে, কনসোল এবং সার্ভার-সাইডে ওয়েবসেম্বলি আই/ও এর জন্য ওয়াসি নামে একটি ডি-ফ্যাক্টো স্ট্যান্ডার্ড রয়েছে। এটি সিস্টেম ভাষাগুলির জন্য সংকলন লক্ষ্য হিসাবে ডিজাইন করা হয়েছিল এবং সমস্ত ধরণের ফাইল সিস্টেম এবং অন্যান্য ক্রিয়াকলাপগুলি একটি traditional তিহ্যবাহী সিঙ্ক্রোনাস আকারে প্রকাশ করে।

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

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

এটি https://wasi.rreverser.com/ এ সরাসরি দেখুন।

অ্যাসিঙ্কাইফাই ব্যবহার-কেসগুলি কেবল টাইমার এবং ফাইল সিস্টেমগুলিতে সীমাবদ্ধ নয়। আপনি আরও যেতে পারেন এবং ওয়েবে আরও কুলুঙ্গি এপিআই ব্যবহার করতে পারেন।

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

সংযুক্ত ক্যানন ক্যামেরা সম্পর্কে তথ্য দেখিয়ে একটি ওয়েব পৃষ্ঠায় লিবাসবি ডিবাগ আউটপুটের স্ক্রিনশট

যদিও এটি সম্ভবত অন্য একটি ব্লগ পোস্টের জন্য একটি গল্প।

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

,

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

আই/ও সিস্টেম ভাষায়

আমি সি -তে একটি সাধারণ উদাহরণ দিয়ে শুরু করব, আপনি কোনও ফাইল থেকে ব্যবহারকারীর নামটি পড়তে চান এবং তাদের "হ্যালো, (ব্যবহারকারীর নাম)" দিয়ে তাদের শুভেচ্ছা জানাতে চান! বার্তা:

#include <stdio.h>

int main() {
    FILE *stream = fopen("name.txt", "r");
    char name[20+1];
    size_t len = fread(&name, 1, 20, stream);
    name[len] = '\0';
    fclose(stream);
    printf("Hello, %s!\n", name);
    return 0;
}

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

সি থেকে নামটি পড়তে, আপনার ফাইলটি খুলতে কমপক্ষে দুটি গুরুত্বপূর্ণ আই/ও কল: fopen , এবং এটি থেকে ডেটা পড়ার জন্য fread প্রয়োজন। একবার আপনি ডেটা পুনরুদ্ধার করার পরে, আপনি ফলাফলটি কনসোলে মুদ্রণ করতে অন্য আই/ও ফাংশন printf ব্যবহার করতে পারেন।

এই ফাংশনগুলি প্রথম নজরে বেশ সহজ দেখায় এবং ডেটা পড়তে বা লেখার জন্য জড়িত যন্ত্রপাতি সম্পর্কে আপনাকে দু'বার ভাবতে হবে না। যাইহোক, পরিবেশের উপর নির্ভর করে ভিতরে অনেক কিছু চলছে:

  • যদি ইনপুট ফাইলটি কোনও স্থানীয় ড্রাইভে অবস্থিত থাকে তবে অ্যাপ্লিকেশনটিকে ফাইলটি সনাক্ত করতে, অনুমতিগুলি পরীক্ষা করতে, পড়ার জন্য এটি খুলতে এবং তারপরে ব্লক দ্বারা পড়তে হবে যতক্ষণ না বাইটের অনুরোধকৃত সংখ্যা পুনরুদ্ধার না করা হয় তার একটি সিরিজ মেমরি এবং ডিস্ক অ্যাক্সেস করতে হবে . আপনার ডিস্কের গতি এবং অনুরোধ করা আকারের উপর নির্ভর করে এটি বেশ ধীর হতে পারে।
  • অথবা, ইনপুট ফাইলটি একটি মাউন্ট করা নেটওয়ার্ক অবস্থানে অবস্থিত হতে পারে, এই ক্ষেত্রে, নেটওয়ার্ক স্ট্যাকটি এখনও জড়িত থাকবে, প্রতিটি ক্রিয়াকলাপের জন্য জটিলতা, বিলম্ব এবং সম্ভাব্য পুনর্বিন্যাসের সংখ্যা বাড়িয়ে তুলবে।
  • অবশেষে, এমনকি printf কনসোলে জিনিস মুদ্রণের গ্যারান্টিযুক্ত নয় এবং এটি কোনও ফাইল বা নেটওয়ার্কের স্থানে পুনঃনির্দেশিত হতে পারে, সেক্ষেত্রে এটি উপরের একই পদক্ষেপের মধ্য দিয়ে যেতে হবে।

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

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

fn main() {
    let s = std::fs::read_to_string("name.txt");
    println!("Hello, {}!", s);
}

তবে আপনি যখন এই নমুনাগুলির কোনওটি ওয়েবসেম্ব্লে সংকলন করার চেষ্টা করেন এবং সেগুলি ওয়েবে অনুবাদ করার চেষ্টা করেন তখন কী ঘটে? বা, একটি নির্দিষ্ট উদাহরণ সরবরাহ করতে, "ফাইল রিড" অপারেশনটিতে অনুবাদ করতে পারে কী? এটি কিছু স্টোরেজ থেকে ডেটা পড়তে হবে।

ওয়েবের অ্যাসিঙ্ক্রোনাস মডেল

ওয়েবে বিভিন্ন ধরণের স্টোরেজ বিকল্প রয়েছে যা আপনি মানচিত্র করতে পারেন, যেমন ইন-মেমরি স্টোরেজ (জেএস অবজেক্টস), localStorage , ইনডেক্সডডিবি , সার্ভার-সাইড স্টোরেজ এবং একটি নতুন ফাইল সিস্টেম অ্যাক্সেস এপিআই।

যাইহোক, এই এপিআইগুলির মধ্যে কেবল দুটি-ইন-মেমরি স্টোরেজ এবং localStorage -সিঙ্ক্রোনালি ব্যবহার করা যেতে পারে এবং উভয়ই আপনি যা সঞ্চয় করতে পারেন এবং কতক্ষণের জন্য সবচেয়ে সীমাবদ্ধ বিকল্প। অন্যান্য সমস্ত বিকল্পগুলি কেবল অ্যাসিনক্রোনাস এপিআই সরবরাহ করে।

এটি ওয়েবে কোড কার্যকর করার মূল বৈশিষ্ট্যগুলির মধ্যে একটি: যে কোনও সময় সাপেক্ষ অপারেশন, যার মধ্যে কোনও আই/ও অন্তর্ভুক্ত রয়েছে, অ্যাসিনক্রোনাস হতে হবে।

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

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

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

এই প্রক্রিয়াটি সম্পর্কে মনে রাখার গুরুত্বপূর্ণ বিষয়টি হ'ল, আপনার কাস্টম জাভাস্ক্রিপ্ট (বা ওয়েবসেমি) কোডটি কার্যকর করার সময়, ইভেন্ট লুপটি অবরুদ্ধ করা হয়েছে এবং এটি হলেও, কোনও বাহ্যিক হ্যান্ডলার, ইভেন্ট, আই/ও, এর প্রতিক্রিয়া জানানোর কোনও উপায় নেই ইত্যাদি। আই/ও ফলাফলগুলি ফিরে পাওয়ার একমাত্র উপায় হ'ল একটি কলব্যাক নিবন্ধন করা, আপনার কোডটি কার্যকর করা শেষ করা এবং ব্রাউজারকে নিয়ন্ত্রণটি ফিরিয়ে দেওয়া যাতে এটি কোনও মুলতুবি কার্যগুলি প্রক্রিয়াজাতকরণ চালিয়ে যেতে পারে। আমি/ও শেষ হয়ে গেলে, আপনার হ্যান্ডলার সেই কাজগুলির মধ্যে একটি হয়ে যাবে এবং কার্যকর করা হবে।

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

async function main() {
  let response = await fetch("name.txt");
  let name = await response.text();
  console.log("Hello, %s!", name);
}

যদিও এটি সিঙ্ক্রোনাস দেখায়, হুডের নীচে প্রতিটি await মূলত কলব্যাকগুলির জন্য সিনট্যাক্স চিনি:

function main() {
  return fetch("name.txt")
    .then(response => response.text())
    .then(name => console.log("Hello, %s!", name));
}

এই ডি-স্যুগার্ড উদাহরণে, যা কিছুটা পরিষ্কার, একটি অনুরোধ শুরু করা হয় এবং প্রতিক্রিয়াগুলি প্রথম কলব্যাকের সাথে সাবস্ক্রাইব করা হয়। ব্রাউজারটি একবার প্রাথমিক প্রতিক্রিয়া গ্রহণ করে - কেবলমাত্র এইচটিটিপি শিরোনামগুলি - এটি অসাধারণভাবে এই কলব্যাকটি আহ্বান করে। কলব্যাকটি response.text() ব্যবহার করে বডিটি পাঠ্য হিসাবে পড়া শুরু করে এবং অন্য কলব্যাকের সাথে ফলাফলের সাবস্ক্রাইব করে। অবশেষে, একবার fetch সমস্ত বিষয়বস্তু পুনরুদ্ধার করে ফেললে, এটি সর্বশেষ কলব্যাকটি অনুরোধ করে, যা "হ্যালো, (ব্যবহারকারীর নাম)!" প্রিন্ট করে! কনসোলে

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

চূড়ান্ত উদাহরণ হিসাবে, এমনকি "স্লিপ" এর মতো সাধারণ এপিআইগুলি, যা কোনও অ্যাপ্লিকেশনকে নির্দিষ্ট কয়েক সেকেন্ডের জন্য অপেক্ষা করে তোলে, এটিও আই/ও অপারেশনের একটি ফর্ম:

#include <stdio.h>
#include <unistd.h>
// ...
printf("A\n");
sleep(1);
printf("B\n");

অবশ্যই, আপনি এটি খুব সোজা পদ্ধতিতে অনুবাদ করতে পারেন যা সময়ের মেয়াদ শেষ না হওয়া পর্যন্ত বর্তমান থ্রেডটি অবরুদ্ধ করে দেয়:

console.log("A");
for (let start = Date.now(); Date.now() - start < 1000;);
console.log("B");

প্রকৃতপক্ষে, এমস্ক্রিপ্টেন তার "ঘুম" এর ডিফল্ট বাস্তবায়নে ঠিক এটিই করেন, তবে এটি অত্যন্ত অদক্ষ, পুরো ইউআইটিকে অবরুদ্ধ করবে এবং এরই মধ্যে অন্য কোনও ইভেন্ট পরিচালনা করতে দেবে না। সাধারণত, উত্পাদন কোডে এটি করবেন না।

পরিবর্তে, জাভাস্ক্রিপ্টে "স্লিপ" এর আরও আইডিয়োমেটিক সংস্করণে setTimeout() কল করা এবং একটি হ্যান্ডলারের সাথে সাবস্ক্রাইব করা জড়িত:

console.log("A");
setTimeout(() => {
    console.log("B");
}, 1000);

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

অ্যাসিঙ্কাইফাইয়ের সাথে ব্যবধানটি ব্রিজ করা

এখানেই অ্যাসিঙ্কাইফায় আসে As অ্যাসিনসিফাই হ'ল একটি সংকলন-সময় বৈশিষ্ট্য যা এমস্ক্রিপ্টেন দ্বারা সমর্থিত যা পুরো প্রোগ্রামটি বিরতি দেয় এবং পরে এটি পুনরায় পুনরায় শুরু করার অনুমতি দেয়।

A call graph
describing a JavaScript -> WebAssembly -> web API -> async task invocation, where Asyncify connects
the result of the async task back into WebAssembly

Usage in C / C++ with Emscripten

If you wanted to use Asyncify to implement an asynchronous sleep for the last example, you could do it like this:

#include <stdio.h>
#include <emscripten.h>

EM_JS(void, async_sleep, (int seconds), {
    Asyncify.handleSleep(wakeUp => {
        setTimeout(wakeUp, seconds * 1000);
    });
});
…
puts("A");
async_sleep(1);
puts("B");

EM_JS is a macro that allows defining JavaScript snippets as if they were C functions. Inside, use a function Asyncify.handleSleep() which tells Emscripten to suspend the program and provides a wakeUp() handler that should be called once the asynchronous operation has finished. In the example above, the handler is passed to setTimeout() , but it could be used in any other context that accepts callbacks. Finally, you can call async_sleep() anywhere you want just like regular sleep() or any other synchronous API.

When compiling such code, you need to tell Emscripten to activate the Asyncify feature. Do that by passing -s ASYNCIFY as well as -s ASYNCIFY_IMPORTS=[func1, func2] with an array-like list of functions that might be asynchronous.

emcc -O2 \
    -s ASYNCIFY \
    -s ASYNCIFY_IMPORTS=[async_sleep] \
    ...

This lets Emscripten know that any calls to those functions might require saving and restoring the state, so the compiler will inject supporting code around such calls.

Now, when you execute this code in the browser you'll see a seamless output log like you'd expect, with B coming after a short delay after A.

A
B

You can return values from Asyncify functions too. What you need to do is return the result of handleSleep() , and pass the result to the wakeUp() callback. For example, if, instead of reading from a file, you want to fetch a number from a remote resource, you can use a snippet like the one below to issue a request, suspend the C code, and resume once the response body is retrieved—all done seamlessly as if the call were synchronous.

EM_JS(int, get_answer, (), {
     return Asyncify.handleSleep(wakeUp => {
        fetch("answer.txt")
            .then(response => response.text())
            .then(text => wakeUp(Number(text)));
    });
});
puts("Getting answer...");
int answer = get_answer();
printf("Answer is %d\n", answer);

In fact, for Promise-based APIs like fetch() , you can even combine Asyncify with JavaScript's async-await feature instead of using the callback-based API. For that, instead of Asyncify.handleSleep() , call Asyncify.handleAsync() . Then, instead of having to schedule a wakeUp() callback, you can pass an async JavaScript function and use await and return inside, making code look even more natural and synchronous, while not losing any of the benefits of the asynchronous I/O.

EM_JS(int, get_answer, (), {
     return Asyncify.handleAsync(async () => {
        let response = await fetch("answer.txt");
        let text = await response.text();
        return Number(text);
    });
});

int answer = get_answer();

Awaiting complex values

But this example still limits you only to numbers. What if you want to implement the original example, where I tried to get a user's name from a file as a string? Well, you can do that too!

Emscripten provides a feature called Embind that allows you to handle conversions between JavaScript and C++ values. It has support for Asyncify as well, so you can call await() on external Promise s and it will act just like await in async-await JavaScript code:

val fetch = val::global("fetch");
val response = fetch(std::string("answer.txt")).await();
val text = response.call<val>("text").await();
auto answer = text.as<std::string>();

When using this method, you don't even need to pass ASYNCIFY_IMPORTS as a compile flag, as it's already included by default.

Okay, so this all works great in Emscripten. What about other toolchains and languages?

Usage from other languages

Say that you have a similar synchronous call somewhere in your Rust code that you want to map to an async API on the web. Turns out, you can do that too!

First, you need to define such a function as a regular import via extern block (or your chosen language's syntax for foreign functions).

extern {
    fn get_answer() -> i32;
}

println!("Getting answer...");
let answer = get_answer();
println!("Answer is {}", answer);

And compile your code to WebAssembly:

cargo build --target wasm32-unknown-unknown

Now you need to instrument the WebAssembly file with code for storing/restoring the stack. For C / C++, Emscripten would do this for us, but it's not used here, so the process is a bit more manual.

Luckily, the Asyncify transform itself is completely toolchain-agnostic. It can transform arbitrary WebAssembly files, no matter which compiler it's produced by. The transform is provided separately as part of the wasm-opt optimiser from the Binaryen toolchain and can be invoked like this:

wasm-opt -O2 --asyncify \
      --pass-arg=asyncify-imports@env.get_answer \
      [...]

Pass --asyncify to enable the transform, and then use --pass-arg=… to provide a comma-separated list of asynchronous functions, where the program state should be suspended and later resumed.

All that's left is to provide supporting runtime code that will actually do that—suspend and resume WebAssembly code. Again, in the C / C++ case this would be included by Emscripten, but now you need custom JavaScript glue code that would handle arbitrary WebAssembly files. We've created a library just for that.

You can find it on GitHub at https://github.com/GoogleChromeLabs/asyncify or npm under the name asyncify-wasm .

It simulates a standard WebAssembly instantiation API , but under its own namespace. The only difference is that, under a regular WebAssembly API you can only provide synchronous functions as imports, while under the Asyncify wrapper, you can provide asynchronous imports as well:

const { instance } = await Asyncify.instantiateStreaming(fetch('app.wasm'), {
    env: {
        async get_answer() {
            let response = await fetch("answer.txt");
            let text = await response.text();
            return Number(text);
        }
    }
});
…
await instance.exports.main();

Once you try to call such an asynchronous function - like get_answer() in the example above - from the WebAssembly side, the library will detect the returned Promise , suspend and save the state of the WebAssembly application, subscribe to the promise completion, and later, once it's resolved, seamlessly restore the call stack and state and continue execution as if nothing has happened.

Since any function in the module might make an asynchronous call, all the exports become potentially asynchronous too, so they get wrapped as well. You might have noticed in the example above that you need to await the result of instance.exports.main() to know when the execution is truly finished.

How does this all work under the hood?

When Asyncify detects a call to one of the ASYNCIFY_IMPORTS functions, it starts an asynchronous operation, saves the entire state of the application, including the call stack and any temporary locals, and later, when that operation is finished, restores all the memory and call stack and resumes from the same place and with the same state as if the program has never stopped.

This is quite similar to async-await feature in JavaScript that I showed earlier, but, unlike the JavaScript one, doesn't require any special syntax or runtime support from the language, and instead works by transforming plain synchronous functions at compile-time.

When compiling the earlier shown asynchronous sleep example:

puts("A");
async_sleep(1);
puts("B");

Asyncify takes this code and transforms it to roughly like the following one (pseudo-code, real transformation is more involved than this):

if (mode == NORMAL_EXECUTION) {
    puts("A");
    async_sleep(1);
    saveLocals();
    mode = UNWINDING;
    return;
}
if (mode == REWINDING) {
    restoreLocals();
    mode = NORMAL_EXECUTION;
}
puts("B");

Initially mode is set to NORMAL_EXECUTION . Correspondingly, the first time such transformed code is executed, only the part leading up to async_sleep() will get evaluated. As soon as the asynchronous operation is scheduled, Asyncify saves all the locals, and unwinds the stack by returning from each function all the way to the top, this way giving control back to the browser event loop.

Then, once async_sleep() resolves, Asyncify support code will change mode to REWINDING , and call the function again. This time, the "normal execution" branch is skipped - since it already did the job last time and I want to avoid printing "A" twice - and instead it comes straight to the "rewinding" branch. Once it's reached, it restores all the stored locals, changes mode back to "normal" and continues the execution as if the code were never stopped in the first place.

Transformation costs

Unfortunately, Asyncify transform isn't completely free, since it has to inject quite a bit of supporting code for storing and restoring all those locals, navigating the call stack under different modes and so on. It tries to modify only functions marked as asynchronous on the command line, as well as any of their potential callers, but the code size overhead might still add up to approximately 50% before compression.

A graph showing code
size overhead for various benchmarks, from near-0% under fine-tuned conditions to over 100% in worst
cases

This isn't ideal, but in many cases acceptable when the alternative is not having the functionality altogether or having to make significant rewrites to the original code.

Make sure to always enable optimizations for the final builds to avoid it going even higher. You can also check Asyncify-specific optimization options to reduce the overhead by limiting transforms only to specified functions and/or only direct function calls. There is also a minor cost to runtime performance, but it's limited to the async calls themselves. However, compared to the cost of the actual work, it's usually negligible.

Real-world demos

Now that you've looked at the simple examples, I'll move on to more complicated scenarios.

As mentioned in the beginning of the article, one of the storage options on the web is an asynchronous File System Access API . It provides access to a real host filesystem from a web application.

On the other hand, there is a de-facto standard called WASI for WebAssembly I/O in the console and the server-side. It was designed as a compilation target for system languages, and exposes all sorts of file system and other operations in a traditional synchronous form.

What if you could map one to another? Then you could compile any application in any source language with any toolchain supporting the WASI target, and run it in a sandbox on the web, while still allowing it to operate on real user files! With Asyncify, you can do just that.

In this demo, I've compiled Rust coreutils crate with a few minor patches to WASI, passed via Asyncify transform and implemented asynchronous bindings from WASI to File System Access API on the JavaScript side. Once combined with Xterm.js terminal component, this provides a realistic shell running in the browser tab and operating on real user files - just like an actual terminal.

Check it out live at https://wasi.rreverser.com/ .

Asyncify use-cases are not limited just to timers and filesystems, either. You can go further and use more niche APIs on the web.

For example, also with the help of Asyncify, it's possible to map libusb —probably the most popular native library for working with USB devices—to a WebUSB API , which gives asynchronous access to such devices on the web. Once mapped and compiled, I got standard libusb tests and examples to run against chosen devices right in the sandbox of a web page.

Screenshot of libusb
debug output on a web page, showing information about the connected Canon camera

It's probably a story for another blog post though.

Those examples demonstrate just how powerful Asyncify can be for bridging the gap and porting all sorts of applications to the web, allowing you to gain cross-platform access, sandboxing, and better security, all without losing functionality.