בפוסט הקודם הסברתי איך עובדים עם ChunkedData הפעם אני רוצה להראות איך פתרתי בעיה שנוצרה לי תוך כדי שימוש בChunkedData .
בדוגמה שלמטה אנחנו פונים לapi שמחזיר אובייקטים (בצורת chunkedData , כלומר אחד אחד ולא את כל האוביקטים ביחד , יכול להחזיר גם חלק מאוביקט…) אנחנו מקבלים את הנתונים , מחלקים את המידע שמגיע מה api לאוביקטים וכל אוביקט שומרים בתוך רשימה בזכרון. במקביל, אנחנו פונים לשרת שוב (בלולאה עד שנגמרים הנתונים) ולוקחים את האוביקטים הקיימים בזכרון לקליינט ומציגים אותו ,ככה אפשר להתחיל להציג את המידע עוד לפני שכולו חוזר מהapi לחווית משתמש טובה יותר.
כמובן יכלנו לשפוך את הנתונים ישירות ממה שחזר לי מה-api ופה נוצרה לי הבעיה. הקליינט לא תמיד מקבל אחד אחד בchunkedData , כי יכול להיות שעד שהוא הגיב השרת שלח כבר עוד מידע ואז אין לנו דרך נורמאלית לדעת כמה אוביקטים חזרו בכל פעם והקוד בקליינט הופך להיות מסורבל מאוד.
לכן מה שעשינו הוא , שימוש בפונקציה אסינכרונית שלא מחזירה דבר, אלא רק ממלאה בזכרון את האוביקטים . ושימוש action רגיל שמחזיר כל פעם את הנתונים הקיימים לי בcache.
לדוגמא נציג את הקוד הבא:
client-
- (function () {
- ‘use strict’;
- angular
- .module(‘chunkedApp’)
- .controller(‘chunkedController’, controller);
- controller.$inject = [‘$scope’, ‘$http’];
- function controller($scope, $http) {
- $scope.chunkedData = “”;
- getData();
- function getData() {
- $http.post(“/Chunked/indexData”, {}).success(function (res) {
- getData2(res.guid);
- })
- }
- function getData2(guid) {
- $http.post(“/Chunked/IndexData2”, { guid: guid }).success(function (res) {
- for (var i = 0; i < res.length; i++) {
- $scope.chunkedData = [$scope.chunkedData, res[i].Chunk].join(” “);
- }
- if (res == “”) {
- alert(“Finish!”);
- return;
- }
- getData2(guid);
- })
- }
- }
- })();
controller-
- namespace WebApplication1.Controllers
- {
- public class ChunkedController : Controller
- {
- Service s = new Service();
- public ActionResult IndexData()
- {
- Guid guid = Guid.NewGuid();
- Task.Run(() =>
- {
- s.Service_Data(guid);
- });
- return Json(new { guid = guid.ToString() });
- }
- public ActionResult IndexData2(Guid guid)
- {
- var result = s.Service_Data(guid);
- return Json(result);
- }
- public ActionResult Api()
- {
- byte[] buffer = null;
- string msg = “Hello world!; Lorem ipsum dolor; sit amet, consectetur adipiscing elit. ;Vestibulum rutrum pulvinar commodo.; Proin vitae posuere augue.;Nam rutrum ex eu ;magna finibus, at faucibus leo mattis.;Vestibulum rutrum dui non nisi; venenatis interdum cursus sed ;magna. Vestibulum placerat; magna magna, vel tempus ligula aliquet ac. Phasellus;egestas efficitur nulla, non pretium lacus tristique;vitae. Nam dignissim ;non erat ac faucibus. In hac; habitasse platea dictumst.; Aenean sollicitudin nisi dui,; vel condimentum erat aliquet vitae.; Nullam feugiat massa vitae tellus ;dictum venenatis. Morbi vel turpis ;sit amet est volutpat volutpat.; Nulla sit amet ex id eros;elementum semper; a a ex. Vestibulum ante ipsum primis in ;faucibus orci luctus; et ultrices posuere cubilia Curae;”;
- for (int i = 0; i < msg.Count(); i++)
- {
- buffer = System.Text.Encoding.UTF8.GetBytes(msg.Skip(i).Take(1).ToArray());
- HttpContext.Response.OutputStream.Write(buffer, 0, buffer.Length);
- HttpContext.Response.Flush();
- }
- HttpContext.Response.Close();
- return null;
- }
- }
- }
service-
- public class Service
- {
- Cache cache = System.Web.HttpContext.Current.Cache;
- public List<ChunkedData> Service_Data(Guid guid)
- {
- if (cache[guid.ToString()] == null)
- {
- cache[guid.ToString()] = new List<ChunkedData>();
- cache[guid.ToString() + “Count”] = 0;
- cache[guid.ToString() + “Finished”] = false;
- Service_CreateApiData(guid);
- return null;
- }
- else
- {
- int count = (int)cache[guid.ToString() + “Count”];
- List<ChunkedData> dataList;
- do
- {
- dataList = (List<ChunkedData>)cache[guid.ToString()];
- if (count == dataList.Count && (bool)cache[guid.ToString() + “Finished”])
- {
- return null;
- }
- } while (dataList.Count – count == 0);
- var copy = dataList.ToList();
- cache[guid.ToString() + “Count”] = copy.Count;
- List<ChunkedData> resultList = copy.Skip(count).ToList();
- return resultList;
- }
- }
- public void Service_CreateApiData(Guid guid)
- {
- List<ChunkedData> dataList = (List<ChunkedData>)cache[guid.ToString()];
- const int bufsize = 32 * 1024;
- List<byte> obj = new List<byte>();
- WebRequest request = WebRequest.Create(“http://localhost:57976/Chunked/Api”);
- using (WebResponse response = request.GetResponse())
- {
- using (Stream responseStream = response.GetResponseStream())
- {
- byte[] buffer = new byte[bufsize];
- MemoryStream memoryStream = new MemoryStream();
- int i = 0, read = 0;
- do
- {
- read = responseStream.Read(buffer, i, buffer.Length);
- string result = Encoding.UTF8.GetString(buffer);
- if (!result.Contains(“;”))
- {
- obj.AddRange(buffer.Take(read));
- }
- else if (read != 0)
- {
- obj.AddRange(buffer.Take(read));
- result = Encoding.UTF8.GetString(obj.ToArray());
- int index = result.LastIndexOf(“;”);
- var endIndex = result.Substring(index + 1);
- var start = result.Substring(0, index);
- var splitArrey = start.Split(‘;’);
- foreach (var item in splitArrey)
- {
- obj = new List<byte>();
- obj.AddRange(Encoding.ASCII.GetBytes(item));
- Serialize(dataList, obj);
- }
- buffer = new byte[bufsize];
- obj = new List<byte>();
- obj.AddRange(Encoding.ASCII.GetBytes(endIndex));
- }
- } while (read != 0);
- cache[guid.ToString() + “Finished”] = true;
- }
- }
- }
- private void Serialize(List<ChunkedData> dataList, List<byte> obj)
- {
- //change the object, Serialization….
- dataList.Add(new ChunkedData() { Chunk = Encoding.UTF8.GetString(obj.ToArray()) });
- }
- public class ChunkedData
- {
- public string Chunk { get; set; }
- }
הסבר-
הקטע קוד הראשון הוא הclient שבעת טעינת העמוד הוא פונה ל-controller ל- action הראשון (“/Chunked/indexData” קטע קוד ראשון שורה 16)
ה-action indexData (קטע קוד שני שורה 7) –
- מייצר guid יחודי לכל פניה.
- מפעיל את הפונקציה Service_Data בצורה אסינכרונית.(כלומר, מפעיל את הפונקציה אך לא מחכה למידע , ולא מחכה עד שתסתיים הפונקציה)
- מחזיר guid ל-client.
בינתיים מה שקורה זה שב-client הוא חזר כאילו הוא סיים.
ובserver מורצת הפונקציה Service_Data (שכבר נראה מה היא עושה) עד שהיא תגמור.
הפונקציה Service_Data (קטע קוד שלישי שורה 4) –
מקבלת את ה-guid שנוצר בcontroller, ובודקת אם קיים כבר מקום בזכרון עם הguid הזה.
אם לא קיים (שזה יקרה רק בפעם הראשונה שנגיע לפונקציה הזאת מה-indexData ):
- מאתחלת מקום בזכרון עם key ה- guid הנ”ל ו- value מסוג List<ChunkedData (ה- ChunkedData הוא אוביקט שמכיל מחרוזת- שורה 97)
- מאתחלת מקום ב זכרון עם key ה- guid +count
- מאתחלת מקום ב זכרון עם key ה- guid +finished
- מפעילה פונקציה Service_CreateApiData–
שהיא פונה לapi שמחזיר chunked data ועושה עליו פעולות.
בדוגמה הנ”ל הפונקציה בודקת אם התו ‘;’ קיים במה שהגיע מה api אם לא היא שומרת מה שהגיע במשתנה obj, אם כן היא בודקת כמה מילים מכיל הobj (מילה זה אומר רשימת תוים עד התו ‘;’) ועל כל מילה היא מפעילה את הפונקציה Serialize שאצלינו היא רק שומרת את המילה בlist שקיים לי בcache.(שורה 91) .
בינתיים בכל הזמן הזה שפועלת הפונקציה Service_CreateApiData הקליינט כבר עובר לעשות את הפעולה הבאה כי הפונקציה הזאת כמו שאמרנו קודם מופעלת בצורה אסינכרונית.
כלומר היא הופעלה ע”י task והcontroller לא חיכה לה והודיע על סיום הפעילות, והגיע בclient לפונציה שמופעלת בתוך ה –success (שורה 17) getData2
שהפונקציה הזאת פונה ל-controller ל- action השני(“/Chunked/indexData2” קטע קוד ראשון שורה 22) עם ה- guid שחזר לה מaction ראשון
ה-action indexData2 (קטע קוד שני שורה 19) –
מפעיל את הפונקציה Service_Data בצורה סינכרונית. כלומר ממתין למידע שיחזור שזה בעצם אותה פוקציה שהראשון הפעיל רק שעכשיו הוא ילך לelse
כלומר עם קיים מקום בcache עם הguid הנ”ל
ומה ששם הוא עושה זה שולף את המידע שנמצא ב cache (שבינתיים מתמלא מהפונקציה Service_CreateApiData שמופעלת.) כל פעם מהמקום האחרון שלקחתי (בכוונה אני לא מוחקת את המידע שלקחתי כי יש מצב שבין הזמן שאני אקח את המידע לזמן שאני אמחק ,יתווסף עוד מידע…) ושולחת לclient שהוא מציג את הנתונים שהוא קיבל ושולח לקבלת המידע הבא עד שהוא מקבל null (מחרוזת ריקה) ואז הוא מציג alert שמודיע על סיום קבלת המידע ומפסיק לשלוח בקשות לשרת.
כמובן ליעול ניתן לקבוע בclient שיפנה לserver פעם בכמה שניות ככה הוא גם יציג מידע וגם לא יפנה מידי הרבה פעמים.
בקצרה התהליך הוא כזה –
בעת טעינה מופעל הindexData שמפעיל את הפונקציה Service_CreateApiData (בעקיפין…) וחוזרת לclient בלי לחכות לסיום. ברגע שחוזר לclient מופעל ה indexData2 (שבמקביל מופעלת הפונקציה Service_CreateApiData ) שלוקחת את הנתונים שקיימים בcache כל פעם מהמקום הקודם, עד לסיום קבלת המידע.
מקווה שעזרתי למישהו…
ניתן להוריד כאן את הקוד המלא.