c# sync async await task - תכנות - HWzone פורומים
עבור לתוכן
  • צור חשבון

c# sync async await task


eido300
 Share

Recommended Posts

הי אשמח לדעת קצת יותר על הנ"ל

אני יודע שsync מכריח שרק theard הנוכחי ירוץ, כל השאר יחכו שהוא יסיים. async מריץ את הtherad במקביל לתהליכים אחרים, מה הולך עם הtask, await, lock? ותכלס, איך כל זה נכנס בעולם האמיתי, כשאני בונה תוכנה, מתי אצטרך להשתמש בזה? עוד לא יצא לי להתקל בצורך כזה (חוץ מאולי לשלוח מייל שזה תוקע את המחשב, וגם לו יש פונקציה מובנית בשביל זה).

תודה.

קישור לתוכן
שתף באתרים אחרים

שאלה גדולה ותשובה ארוכה, אנסה להתפנות לכתוב תשובה בהמשך היום.

 

בקצרה, זה ממש הפוך (בהנחה שבasync אתה מתכוון לasync/await ולא סתם asynchronous programming): אם אתה קוד סינכרוני, הthread הנוכחי נחסם ולא יכול לבצע קוד אבל threadים אחרים ממשיכים לרוץ במקביל. אם אתה קוד async (עם await), גם בthread הנוכחי וגם בthreadים אחרים יכול לרוץ קוד.

קישור לתוכן
שתף באתרים אחרים

ציטוט של af db creid

שאלה גדולה ותשובה ארוכה, אנסה להתפנות לכתוב תשובה בהמשך היום.

 

בקצרה, זה ממש הפוך (בהנחה שבasync אתה מתכוון לasync/await ולא סתם asynchronous programming): אם אתה מבצע קוד סינכרוני, הthread הנוכחי נחסם ולא יכול לבצע קוד אבל threadים אחרים ממשיכים לרוץ במקביל. אם אתה מבצע קוד async (עם await), גם בthread הנוכחי וגם בthreadים אחרים יכול לרוץ קוד.

תודה רבה 🙂

 

התכוונתי לasync/await אבל יכול להיות שערבבתי קצת עם asynchronous programming...

קישור לתוכן
שתף באתרים אחרים

טוב. אמנם יש לי עוד הרבה דברים לעשות, אבל הגעתי למסקנה שזה לא יפה שאני דוחה אותך ככה, אז אשב לכתוב את הפוסט ואגמור עם זה.

 

הסיבה שאנחנו רוצים async/await היא כדי להקל עלינו את הכתיבה, ויותר חשוב, הקריאה של קוד.

אבל זה לא עונה על השאלה הבסיסית יותר: למה לא להשתמש בthreads? ומה ההבדל בין threads לasync/await או מה שקדם לו (ספציפית, callback-style וPromises לjs וTPL ל.NET)?

 

בוא נתחיל עם js, כי היא פשוטה יותר להבין.

 

JS היא single-threaded. זה אומר שיש אך ורק thread אחד ולא יותר (כיום זה כבר לא נכון, אבל עדיין זה המודל הבסיסי ופעם זה מה שהיה).

מדוע? יש מספר יתרונות למודל הזה: אל"ף, הוא מפשט בהרבה את מנועי הJS, כי ככה הם לא צריכים לדאוג לthread safety. אפילו מפתחי Python, שיש להם ספריית threading, יש להם GIL (ר"ת של Global Interpreter Lock) שפירושו שהמפרש של פייתון אינו thread-safe (דבר שהיה מאט אותו בצורה ניכרת), ולכן threads לא באמת מריצים קוד במקביל. אם אתה רוצה הרצת קוד במקביל אתה צריך להשתמש במודול multiprocessing שבמקום threads מוציא processes (שיקרים הרבה יותר).

שנית, בל נשכח שJS נולדה עבור הדפדפנים כשפה לאנימציות ולא הרבה יותר. מקסימום אימות של טפסים. אני לא חושב שמתכנני השפה האמינו שהיא תהפוך להיות השפה הנפוצה בעולם...

אבל לצד שרת, אף אחד לא חלם אפילו להשתמש בJS. והיתה לזה סיבה טובה.

שרתים מוכרים, כמו PHP וASP (וגם ASP.NET), פעלו בדרך של threads: כל בקשה שנשלחת לשרת מקבלת thread משלה. ככה אפשר לטפל בכל בקשה בנפרד, בלי להסתבך עם בקשות אחרות.

אבל לדרך הזו היה חיסרון בולט: כמו שאמרתי, context switch. זה דבר יקר. שרתים בתקופה הזו יכלו לטפל, בממוצע, בכעשרת אלפים בקשות בשנייה. אתה מקבל יותר בקשות? תוסיף שרת, בבקשה.

ב2009 קם מתכנת בשם Ryan Dahl והכריז: אני רוצה ליצור את Node.js, JS עבור שרתים. אבל היא לא תעבוד בדרך הרגילה, כי בJS אין threads, כזכור.

במקום זאת, הוא אמר: מהו הדבר האיטי ביותר ששרתים מבצעים? שליחת קריאות . ואחריו? קריאה מהדיסק.

עד כמה זה יותר איטי? ובכן, אם גישה למטמון L1 הייתה לוקחת שנייה אחת - רק שנייה אחת! אזי קריאת 4 קילובייט אקראיים מSSD הייתה לוקחת רבע שעה. לא פחות! ונחש כמה זמן היה לוקח לשלוח פקטה - אחת! מקליפורניה להולנד ובחזרה? ספוילר: יותר מ13 וחצי שנים!!!

למזלנו, גישה לL1 לא לוקחת שנייה אלא כחצי מיליארדית השנייה. אבל הפקטור הוא אותו פקטור. מה שמאט את השרת זה לא החישובים שלו - זה התקשורת עם העולם החיצון.

והוא עושה הרבה מזה: כל שליחת תגובה זה שליחת הרבה פקטות. ובשביל כל תגובה כזו, אנחנו קוראים מהדיסק את הtemplate (או מהRAM, אבל כנראה מהדיסק).

חשב Ryan לעצמו, האם יש אלטרנטיבה? ובכן, יש: והיא אפילו היתה קיימת כבר הרבה הרבה זמן. קוראים לה Event Driven Programming.

מהו Event Driven Programming? ובכן, נניח שאני מממש GUI. ויש לי שני כפתורים. עכשיו, אני רוצה לעשות משהו כשהכפתור נלחץ. דרך פשוטה היא להשתמש בלולאה:

while (true)
{
  if (button1.Clicked)
  {
    // Handle click...
  }
}

אבל בגישה הזו יש חיסרון בולט: אני לא יכול לטפל במקביל בשני הכפתורים. אז אפשר להוסיף thread נוסף, אבל זה יקר. במקום זאת, אפשר להוסיף עוד if:

while (true)
{
  if (button1.Clicked)
  {
    // Handle button1 click...
  }
  
  if (button2.Clicked)
  {
    // Handle button2 click...
  }
}

אבל רגע, זה לא איך שאנחנו עובדים עם ספריות GUI מודרניות! נכון. כי זה מסובך ומועד לטעויות. במקום זאת, הספריה שמה לולאה כזו מאחורי הקלעים, ונותנת לנו לרשום callbacks.

מהו callback? זוהי פונקציה שנקרא לה כאשר מאורע מסוים מתרחש. בC# זה ממומש באמצעות delegates או events. ככה:

public class MyForm : System.Windows.Forms.Form
{
  private System.Windows.Forms.Button _Button1;
  private System.Windows.Forms.Button _Button1;
  
  public MyForm()
  {
    InitializeComponent();
  }
  
  private void InitializeComponent()
  {
    _Button1 = new System.Windows.Forms.Button();
    _Button2 = new System.Windows.Forms.Button();
    Controls.Add(_Button1);
    Controls.Add(_Button2);
    // Some more stuff...
    _Button1.Click += Button1_Click;
    _Button2.Click += Button2_Click;
  }
  
  private void Button1_Click(object sender, System.EventArgs e)
  {
    System.Windows.Forms.MessageBox.Show("Button 1 clicked!");
  }
  
  private void Button2_Click(object sender, System.EventArgs e)
  {
    System.Windows.Forms.MessageBox.Show("Button 2 clicked!");
  }
}

מאחורי הקלעים, כך (כמובן שזה ממש מפושט) נראה הקוד של WinForms:

while (true)
{
  Message message = GetMessage();
  switch (message.Type)
  {
    case MessageType.MouseLeftButtonClick:
      foreach (Button button in _Buttons)
      {
        if (IsInsideRegion(button.Region, message.MousePosition))
        {
          button.Click?.Invoke(button, EventArgs.Empty);
        }
      }
      break;
    // More events...
  }
}

ושוב, למעשה זה לא עובד ככה, אבל זה העיקרון.

 

JS, במיוחד, מתמחית בEvent-Driven Programming, ומתכנתי JS הכירו מאז ומתמיד את המושג callback. לדוגמה, form.submit זה אירוע (event). אבל callbacks לא שימשו רק לזה: לדוגמה, איך אני מושך מידע מהאינטרנט בPython?

import requests # An external pip library, very common though

url = 'https://google.com'
res = requests.get(url)
res = res.text
print(res)

אבל זה blocking operation: בזמן שהקוד הנ"ל מחכה לתשובה, שום דבר לא קורה. בJS אי אפשר לעשות דבר כזה, כי גם הHTML רץ באותו thread: אם נחסום את הthread, הדף יקפא ולא יוכל לבצע שום אינטרקציה עם המשתמש. זו כמובן לא חוויה ידידותית במיוחד, ולכן משתמשים בcallbacks (הערה חשובה: זה אינו קוד JS מודרני, אלא איך שנכתב בעבר):

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 /* Completed */) {
    if (xhr.status === 200 /* HTTP OK */) {
      var response = xhr.responseText;
      // Process response...
    } else {
      alert("An error occurred");
    }
  }
};
xhr.open('GET', '/my-page');
xhr.send();

כמו שאתה רואה, אנחנו לא מחכים שהבקשה תסתיים: במקום זאת, אנחנו אומרים לדפדפן "כשהבקשה מסתיימת, תעיר אותי" - או, תקרא לcallback שלי.

 

ואיך מחכים שנייה בפייתון?

from time import sleep

sleep(1)
print('Waked up')

ואיך בJS?

setTimeout(function() {
  alert('Waked up');
}, 1000);

(ההבדל הוא כי בפייתון sleep מקבלת את הארגומנט שלה בשניות ובJS באלפיות השנייה).

 

וRyan אמר, בNode שלי, יש רק thread אחד. אבל אם אתה רוצה לקרוא את הtemplate שלך, או להתחבר למסג נתונים, או לבקש את נתוני תחנת החלל הבינלאומית, אתה לא חוסם את הthread - אתה פשוט שם callback.

 

וזה עובד נהדר. ובאמת, לחישובים כבדים Node.js לא כ"כ מתאימה (שוב, היום זה כבר השתנה), אבל לשרתים רגילים היא עובדת כמו קסם, ויעילה בהרבה מהשיטות המסורתיות. כל כך יותר יעילה, שכמעט כולם אימצו את הדרך שלה.

 

אבל ישנה בעיה אחת, מהותית, לא קלה בכלל, עם callbacks - ויש לה אפילו שם: היא נקראת callback hell.

וככה זה נראה, הגיהנום:

app.post('/register', function(req, res) {
  const { username, password } = req.body;
  dbConnection.getByUserName(username, function(err, user) {
    if (err) {
      log(err);
      res.status(500 /* Internal server error */).end();
      return;
    }
    
    if (user) {
      fs.readFile('../templates/register/user-already-exists.html', function(err, template) {
        if (err) {
          log(err);
          res.status(500).end();
          return;
        }
        
        res.status(400 /* Bad request */).send(template);
      });
      return;
    }
    
    bcrypt.genSalt(10, function(err, salt) {
      if (err) {
        log(err);
        res.status(500).end();
        return;
      }
      
      bcrypt.hash(password, salt, function(err, passwordHash) {
        if (err) {
          log(err);
          res.status(500).end();
          return;
        }
        
        dbConnection.addUser({ username, passwordHash }, function(err) {
          if (err) {
            log(err);
            res.status(500).end();
            return;
          }
          
          fs.readFile('../templates/register/success.html', function(err, template) {
            if (err) {
              log(err);
              res.status(500).end();
              return;
            }
            
            res.status(200 /* OK */).send(template);
          });
        });
      });
    });
  });
});

או לפי סימן ההיכר של הcallback hell:

              });
            });
          });
        });
      });
    });
  });
});

זה אמיתי. לא מעט קודים אכן נכתבו ככה.

 

אתה בוודאי מבין שהבעיה היא חוסר הקריאות הנורא של הקוד. אבל זה יותר גרוע. קשה לעקוב אחרי הזרימה של הקוד. זו הסיבה העיקרית שאנחנו שונאים goto - הוא מבלבל את הקורא (וגם את הקומפיילר, אבל זה כבר סיפור אחר). קשה להבין לאן הולך כל קטע קוד. אם אבקש ממך להצביע לקטע הקוד שמטפל במקרה שהמשתמש כבר קיים ונכשלנו לקרוא את הHTML, כמה זמן יקח לך למצוא אותו?

 

תשווה את זה לדבר הבא:

app.post('/register', function(req, res) {
  const { username, password } = req.body;
  try {
    const user = dbConnection.getByUserNameSync(username);
    
    if (user) {
      const template = fs.readFileSync('../templates/register/user-already-exists.html');
      res.status(400 /* Bad request */).send(template);
      return;
    }
    
    const salt = bcrypt.genSaltSync(10);
    const passwordHash = bcrypt.hashSync(password, salt);
    dbConnection.addUserSync({ username, passwordHash });
    const template = fs.readFileSync('../templates/register/success.html');
    res.status(200 /* OK */).send(template);
  } catch (err) {
    log(err);
    res.status(500).end();
  }
});

קצר יותר, קריא הרבה יותר, והכי חשוב: קל בהרבה לעקוב אחרי הזרימה של הקוד. למעשה, כששכתבתי את הגירסה למעלה לגירסה הזו מצאתי בה שגיאה. ונכון שאת אותה השגיאה הייתי מוצא גם בריצה הראשונה של הקוד (מה שלא היה קורה אם זה היה באחד מנתיבי השגיאה - כלומר קורה רק אם קרתה שגיאה), או אם הייתי משתמש בTypeScript, אבל זה רק מדגים עד כמה קל יותר להבין את העניין.

 

אז את זה פתרו עם ......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

Promises.

 

מה, חשבת שאני אגיד async/await? הו, לא. לא כל כך מהר.

 

אבל בכל זאת לשם הגענו בסוף, כי באמת promises, על אף שהיוו התקדמות משמעותית, לא היו מספיק. אז איך הקוד מלמעלה בגירסת async/await?

app.post('/register', async function(req, res) {
  const { username, password } = req.body;
  try {
    const user = await dbConnection.getByUserName(username);
    
    if (user) {
      const template = await fs.promises.readFile('../templates/register/user-already-exists.html');
      res.status(400 /* Bad request */).send(template);
      return;
    }
    
    const salt = await bcrypt.genSalt(10);
    const passwordHash = await bcrypt.hash(password, salt);
    await dbConnection.addUser({ username, passwordHash });
    const template = await fs.promises.readFile('../templates/register/success.html');
    res.status(200 /* OK */).send(template);
  } catch (err) {
    log(err);
    res.status(500).end();
  }
});

הרבה יותר דומה לגירסה הסינכרונית מאשר לגירסת הcallback, נכון? ותוכיח העובדה שהעתקתי מהגירסה הסינכרונית ולא מגירסת הcallbacks... :P

 

בפוסט הבא: אז איך הקוד של async/await עובד, מה זה promises, מה זה TPL (ר"ת של Task Parallel Library) ואיך בכל זאת C# שונה מJS. המשך יבוא :)

קישור לתוכן
שתף באתרים אחרים

  • 2 שבועות מאוחר יותר...
  • 3 שבועות מאוחר יותר...

הצטרפ/י לדיון

בשלב זה תוכל/י להצטרף לדיון, ולאחר מכן להצטרף לקהילה שלנו. אם כבר יש לך חשבון אצלנו, אנא התחבר/י עכשיו על מנת להגיב תחת שם המשתמש שלך.
לתשומת לבך: התגובה תופיע לגולשים לאחר אישור של צוות הנהלת הפורומים.

אורח
הוסף תגובה

×   התוכן שהודבק הוא עם עיצוב.   הסר עיצוב

  Only 75 emoji are allowed.

×   הקישור שלך הוטמע אוטומטית.   הצג כקישור רגיל

×   התוכן הקודם שלך שוחזר אוטומטית.   נקה הכל

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
  • צור חדש...