כיצד לעבוד עם ConcurrentBag ו- ConcurrentDictionary ב- .Net

אוספים בו-זמניים ב- .Net כלולים בתוך מרחב השמות System.Collections.Concurrent ומספקים יישומים ללא נעילה ובטוחים בשרשור של שיעורי האוסף. אוספי חוטים מובטלים הוצגו לראשונה ב- .Net 4, ואוספים הוצגו לראשונה כחלק מ- Net Framework 1.0 והיו זמינים במרחב השמות System.Collections.

אתה יכול לנצל את האוספים בו-זמנית כדי לעבוד עם אוספים ללא הצורך לכתוב כל קוד נוסף לסינכרון החוטים. אתה יכול להעיף מבט במאמר שלי על ConcurrentStack ו- ConcurrentQueue.

ConcurrentBag

ה- ConcurrentBag מספק אוסף בטוח של חוטים של מערכת אלמנטים לא מסודרת. הנה רשימת השיטות החשובות של כיתת ConcurrentBag.

  • הוסף (אלמנט T) - שיטה זו משמשת להוספת אלמנט ל- ConcurrentBag.
  • TryPeek (out T) - שיטה זו משמשת לאחזור אלמנט מ- ConcurrentBag מבלי להסיר אותו.
  • TryTake (out T) - שיטה זו משמשת לאחזור אלמנט מ- ConcurrentBag. שים לב ששיטה זו מסירה את הפריט מהאוסף.

קטע הקוד הבא ממחיש כיצד ניתן ליצור אוסף ConcurrentBag ולאחסן בו פריטים.

ConcurrentBag concurrentBag = new ConcurrentBag();

  for (int i = 0; i < 10; i++)

    {

        concurrentBag.Add(i);

    }

אם היית מאחזר את הפריטים באוסף, עליך לכתוב את הקוד הבא:

while (concurrentBag.Count > 0)

  {

      Int32 element;

      if (concurrentBag.TryTake(out element))

       {

         Console.WriteLine(element);

       }

  }

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

static void Main(string[] args)

        {

            ConcurrentBag concurrentBag = new ConcurrentBag();

            for (int i = 0; i < 10; i++)

            {

                concurrentBag.Add(i);

            }

            while (concurrentBag.Count > 0)

            {

                Int32 element;

                if (concurrentBag.TryTake(out element))

                {

                    Console.WriteLine(element);

                }

            }

            Console.Read();

        }

במקביל מילון

מילון הוא אוסף כללי של זוגות מפתח / ערך. זה מהיר יותר מ- Hashtable מכיוון שהוא מבטל את תקורות האגרוף והאי-אגרוף. ConcurrentDictionary נכלל במרחב השמות System.Collections.Concurrent ומייצג מילון בטוח לשרשור.

החברים החשובים בשיעור ConcurrentDictionary כוללים את הדברים הבאים:

  • TryAdd: שיטה זו משמשת להוספת פריט במופע ConcurrentDictionary. שים לב ששיטה זו מציגה חריג אם המפתח כבר קיים באוסף.
  • TryGetValue: שיטה זו משמשת לאחזור פריט מהאוסף.
  • TryRemove: שיטה זו משמשת להסרת פריט מהאוסף.
  • TryUpdate: שיטה זו משמשת לעדכון מפתח מסוים במופע ConcurrentDictionary עם הערך החדש שסופק.

קטע הקוד הבא מראה כיצד ניתן ליצור מופע ConcurrentDictionary ולהוסיף לו פריטים:

ConcurrentDictionary obj = new ConcurrentDictionary();

obj.TryAdd("X001", "This is the first value.");

obj.TryAdd("X002", "This is the second value.");

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

bool success = obj.TryAdd("X002", "This is the third value.");

הערך של משתנה ההצלחה הוא "שקר" מכיוון שהניסיון להוסיף ערך עם אותו מפתח נכשל.

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

string item = null;

bool isExist = obj.TryGetValue("X001", out item);

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

foreach(var v in obj)

    {

        Console.WriteLine(v.Key + "---" + v.Value);

    }

קטע הקוד הבא מראה כיצד ניתן להסיר פריט מהאוסף.

string item = null;

bool result = obj.TryRemove("X001", out item);

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

obj.Clear();

כעת שקול את שתי השיטות הסטטיות הבאות.

static void FirstTask(ConcurrentDictionary obj)

        {

            for (int i = 0; i < 10; ++i)

            {

                obj.TryAdd(i.ToString(), i.ToString());

                Thread.Sleep(100);

            }

        }

        static void SecondTask(ConcurrentDictionary obj)

        {

            Thread.Sleep(1000);

            foreach (var item in obj)

            {

                Console.WriteLine("Key: "+item.Key + "   Value: " + item.Value);

                Thread.Sleep(100);

            }

        }

כך תוכל לבצע את שתי השיטות הנ"ל בשני מופעי משימה בו זמנית - האחת לאחסון ערכים באוסף והשנייה לקריאת ערכים מהאוסף.

ConcurrentDictionary obj = new ConcurrentDictionary();

Task firstTask = Task.Run(() => FirstTask(obj));           

Task secondTask = Task.Run(() => SecondTask(obj));           

try

{

  Task.WaitAll(firstTask, secondTask);

}

catch (AggregateException ex)

{

   //Write your own code here to handle exception

}

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