פתרון חוזה פעולה חופפת¶
ניצור אובייקט בקשה שמאגד את כל הזיכרון שהמערכת רשאית להשתמש בו. הדוגמה הבאה היא הגדרת מבנה לשילוב בתוכנית ההרצאה, ולא תוכנית עצמאית:
struct Request {
OVERLAPPED operation{};
std::array<std::byte, 4096> buffer{};
HANDLE event = nullptr;
bool pending = false;
DWORD bytes = 0;
DWORD error = ERROR_SUCCESS;
};
אפשר להחזיק שתי בקשות בתוך std::array בעל כתובת יציבה במשך כל הפעולות. עבור מספר דינמי אפשר להשתמש באובייקטים נפרדים המוחזקים באמצעות unique_ptr, כל עוד אין מוחקים אותם לפני השלמה. הזזת unique_ptr אינה מזיזה את האובייקט שאליו הוא מצביע; הזזת Request עצמו כן יכולה לשנות כתובות שהמערכת כבר מכירה.
רצף ההגשה והאיסוף¶
לפני כל ReadFile מאתחלים את offset ואת event של אותה בקשה. אם הפעולה הושלמה מיד, שומרים את המידע הדרוש לאיסוף התוצאה. אם חזרה ERROR_IO_PENDING, מסמנים שהבקשה עדיין פעילה. אם התקבלה שגיאת הגשה אחרת, שומרים אותה ולא ממתינים לאירוע שלא צפוי להגיע.
הביטול הממוקד משתמש בכתובת &requests[1].operation, ולא ב־nullptr שמרחיב את הבקשה לכל הפעולות המתאימות על ה־handle. לאחר מכן אוספים תוצאה סופית לכל פעולה שהתקבלה. רק אחרי ששתי הפעולות הסתיימו מותר לצאת מן התחום שמחזיק את מערך הבקשות.
הטיפול בשגיאה בהקמת האירוע השני חייב להשאיר את הראשון סגור כראוי. רצוי להקים את כל משאבי הבקשות לפני הגשת הפעולה הראשונה. כך כשל הקמה אינו מחייב פתאום לבטל פעולה שכבר התחילה. אם העיצוב מאפשר הגשה הדרגתית, מסלול rollback חייב לבטל ולאסוף את הפעולות שכבר התקבלו.
מרוץ חוקי עם הביטול¶
תרחיש אפשרי הוא שהבקשה הסתיימה בקרנל, אבל הקוד שלנו עדיין לא אסף את תוצאתה. קריאת CancelIoEx אינה מוצאת פעולה ממתינה ומחזירה ERROR_NOT_FOUND. עדיין צריך לקרוא GetOverlappedResult ולבחון את התוצאה. שחרור ה־context על סמך קוד הביטול בלבד מערבב בין איתור פעולה לביטול לבין אימות סיומה.
תרחיש אחר הוא שהביטול התקבל, אך הפעולה מסיימת בהצלחה לפני שהביטול משנה את תוצאתה. הדוח צריך לשמור בנפרד את העובדה שביקשנו ביטול ואת תוצאת ההשלמה בפועל. הממשק יכול לבחור לא לפרסם נתונים אחרי בקשת ביטול, אבל הניקוי עדיין מבוסס על ההשלמה.
סכנת שינוי הכתובת¶
אם vector של Requestים גדל, הוא עשוי להעתיק או להזיז את אובייקטי הבקשה לאחסון אחר. הקרנל עדיין מחזיק את הכתובת שנמסרה בהגשה. שמירת מספר האינדקס של הבקשה אינה מתקנת את הכתובת הישנה. גם reserve אינו פתרון כללי אם בעתיד עוברים את הקיבולת שהוזמנה; החוזה צריך למנוע שינוי כתובות, לא לקוות שהוא לא יקרה.
הקריאה הבאה בפרק תשתמש בכתובת OVERLAPPED שמגיעה מתוך חבילת completion. לכן הכתובת תהיה גם מפתח זיהוי וגם חלק מחוזה החיים. שני התפקידים מחייבים שהבקשה תישאר קיימת עד שהמנוע צרך את החבילה הסופית שלה.