לדלג לתוכן

פתרון גבולות I/O אסינכרוני

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

static async Task CopyNewAsync(string source, string target, CancellationToken token)
{
    await using var input = new FileStream(source, FileMode.Open, FileAccess.Read,
        FileShare.Read, 1, FileOptions.Asynchronous | FileOptions.SequentialScan);
    await using var output = new FileStream(target, FileMode.CreateNew, FileAccess.Write,
        FileShare.None, 1, FileOptions.Asynchronous);
    byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(64 * 1024);
    try
    {
        while (true)
        {
            int count = await input.ReadAsync(buffer.AsMemory(0, 64 * 1024), token);
            if (count == 0) break;
            await output.WriteAsync(buffer.AsMemory(0, count), token);
        }
        await output.FlushAsync(token);
    }
    finally { System.Buffers.ArrayPool<byte>.Shared.Return(buffer); }
}

ה־FlushAsync אינו מבטיח בפני עצמו עמידות מלאה מול אובדן חשמל. אם המוצר דורש commit עמיד, נשלב את פרוטוקול האחסון והחלפת הקובץ שנלמד קודם ונבדוק את חוזה ה־filesystem. ארבע פעולות דורשות לפחות ארבעה טווחים של 64 KiB, וה־pool רשאי להחזיר מערכים גדולים יותר. המדידה צריכה לכלול גם buffers פנימיים ונתונים אחרים שמוחזקים במקביל.

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