แนวทางงานที่แตกต่างกันทำงานแตกต่างกัน [ซ้ำกัน]

ฉันต้องการถามว่าทำไมทั้งสองวิธีนี้จึงแตกต่างกันและส่งกลับชุดค่าที่ต่างกันสองชุด:

อันแรกที่ถูกต้องในความคิดของฉันคืนค่าจาก 0 ถึง 8 และใช้ได้กับเธรดที่แตกต่างกัน (รหัส LINQPad):

void Main()
{
    var newTasks = Enumerable.Range(0, 9).Select(x => Task.Run(() => DoSomething(x)));

    Task.WhenAll(newTasks);
}    

public int DoSomething(int value)
{
    return value;
}

ประการที่สอง ซึ่งไม่ถูกต้องในความคิดของฉัน ซึ่งคืนค่าสุ่มเป็น 9 เป็นพิเศษ แต่ยังใช้ได้กับเธรดที่แตกต่างกันด้วย

void Main()
{
    var tasks = new List<Task<int>>();
    
    for (var index = 0; index < 9; index++)
    {
        var task = Task.Run(() => DoSomething(index));
        
        tasks.Add(task);
    }
    
    Task.WaitAll(tasks.ToArray());
}    

public int DoSomething(int value)
{
    return value;
}

เป็นไปได้ไหมที่จะแก้ไขอันที่สองแล้วได้ผลลัพธ์คล้ายกับตัวอย่างแรก?

ขอบคุณสำหรับคำตอบของคุณ


person azuremycraj    schedule 28.05.2021    source แหล่งที่มา
comment
ที่เกี่ยวข้อง: ตัวแปรที่บันทึกในลูปใน C#   -  person Theodor Zoulias    schedule 28.05.2021


คำตอบ (1)


เป็นไปได้ไหมที่จะแก้ไขอันที่สองแล้วได้ผลลัพธ์คล้ายกับตัวอย่างแรก?

เมื่อคุณส่งตัวแปรหรือวัตถุไปที่ Task.Run(()=> DoSomething(index)) คุณจะ จับ ตัวแปร

ในแง่หนึ่ง คุณไม่เพียงแต่ส่งตัวแปรนั้นเป็นค่าหรืออ็อบเจ็กต์เท่านั้น แต่ยังให้เธรดนั้นเข้าถึงตัวแปรดั้งเดิมได้โดยตรง (นี่เป็นเรื่องทั่วไปอย่างมากและมีความแตกต่างมากมาย)

ปัญหาที่คุณน่าจะพบมากที่สุดคือ CLR กำลังพยายามจับ index ซึ่งเป็น ตัววนซ้ำ ที่ขอบเขตไม่สามารถออกจากขอบเขตได้ เว้นแต่ความพยายามใด ๆ ที่จะทำเช่นนั้นจะยังคงอยู่ในสแต็ก (โดยพื้นฐานแล้วเธรดเดียวกัน เป็นการทั่วไปอีกครั้ง)

เมื่อ CLR 'จับ' index มันจะจับเฉพาะค่าสุ่มที่ index เป็น (เนื่องจากสภาพการแข่งขัน) - หรือค่า สุดท้าย ที่ index เป็น ซึ่งก็คือ สองเท่า un - ใช้งานง่ายเพราะคุณบอกอย่างชัดเจนว่า index ไม่ควรไปถึง 9 ในบรรทัดนี้ที่นี่ for (var index = 0; index < 9; index++)

นี่เป็นสิ่งที่ยุ่งยาก เมื่อ CLR จับ index มันจะจับค่าใดก็ตามที่เป็นค่าสุดท้ายของ index แต่หลังจากการวนซ้ำสิ้นสุด index จะเพิ่มขึ้นอีกครั้ง ทำให้ 9 เป็นค่าสุดท้ายของ index

นี่อาจทำให้เกิดปัญหามากมาย โดยเฉพาะอย่างยิ่งหากคุณทำงานกับอาร์เรย์ เรามาเลย IndexOutOfBoundsException!

วิธีแก้ไข
การแก้ไขทำได้ง่ายมาก! เพียงสร้างตัวแปรชั่วคราวในลูปแล้วส่งต่อแทน index โดยตรง

นี่คือตัวอย่าง:

void Main()
{
    var tasks = new List<Task<int>>();
    
    for (var index = 0; index < 9; index++)
    {
        int tmpIndex = index;

        var task = Task.Run(() => DoSomething(tmpIndex ));
        
        tasks.Add(task);
    }
    
    Task.WaitAll(tasks.ToArray());
}

เป็นไปได้ไหมที่จะแก้ไขอันที่สองแล้วได้ผลลัพธ์คล้ายกับตัวอย่างแรก?

หมายเหตุด้านข้าง
อย่าพึ่งพาข้อมูลจากงานเพื่อให้เป็นไปตามลำดับ เว้นแต่คุณจะดำเนินการรับประกันอย่างชัดเจนว่ารายการจะกลับมาตามลำดับ TaskScheduler เลือก Tasks เพื่อให้ทำงานตามปกติใน FIFO(เข้าก่อนก่อนออก) อย่างไรก็ตาม หาก TaskScheduler ปรับให้เหมาะสม จัดลำดับความสำคัญ หรือเลื่อนงานไปรอบๆ (ซึ่งทำบ่อยครั้ง) งานของคุณจะ ไม่ เสร็จสมบูรณ์ตามลำดับ และผลลัพธ์ที่คุณได้รับจะไม่ตามลำดับในภายหลัง

person DekuDesu    schedule 28.05.2021