Pendekatan Tugas yang berbeda bekerja secara berbeda [duplikat]

Saya ingin bertanya mengapa kedua pendekatan ini berbeda dan mengembalikan dua rangkaian nilai yang berbeda:

Yang pertama yang benar menurut saya mengembalikan nilai dari 0 hingga 8 dan berfungsi pada thread yang berbeda (kode 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;
}

Kedua, yang menurut saya tidak benar yang mengembalikan nilai acak khususnya 9 tetapi juga berfungsi pada utas yang berbeda.

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;
}

Apakah mungkin untuk memodifikasi yang kedua dan mendapatkan hasil yang mirip dengan contoh pertama?

Terima kasih atas jawaban Anda.


person azuremycraj    schedule 28.05.2021    source sumber
comment
Terkait: Variabel yang diambil dalam satu loop di C#   -  person Theodor Zoulias    schedule 28.05.2021


Jawaban (1)


Apakah mungkin untuk memodifikasi yang kedua dan mendapatkan hasil yang mirip dengan contoh pertama?

Saat Anda meneruskan variabel atau objek ke Task.Run(()=> DoSomething(index)) Anda menangkap sebuah variabel.

Dalam arti tertentu Anda tidak hanya mengirimkan variabel itu sebagai nilai atau objek, tetapi memberikan thread tersebut akses langsung ke variabel asli. (Ini sangat umum dan memiliki banyak nuansa).

Masalah yang paling mungkin Anda temui adalah CLR mencoba menangkap index, yang merupakan iterator yang cakupannya tidak dapat meninggalkan batasnya kecuali upaya apa pun untuk melakukannya akan tetap ada di tumpukan (pada dasarnya thread yang sama , sekali lagi terlalu digeneralisasikan).

Ketika CLR 'menangkap' index ia hanya akan menangkap nilai acak yaitu index (karena kondisi balapan) - atau nilai terakhir yaitu index, yaitu double un -intuitif karena Anda secara eksplisit mengatakan bahwa index tidak boleh sampai ke 9 di baris ini di sini for (var index = 0; index < 9; index++).

Inilah hal rumitnya, ketika CLR menangkap index, ia menangkap berapa pun nilai terakhir untuk index. Namun setelah loop berakhir index bertambah sekali lagi menyebabkan 9 menjadi nilai terakhir dari index.

Hal ini dapat menyebabkan banyak masalah, terutama jika Anda bekerja dengan array, inilah kami IndexOutOfBoundsException!

Cara memperbaikinya
Cara memperbaikinya sebenarnya sangat sederhana! Cukup buat variabel sementara di loop, dan teruskan variabel tersebut alih-alih index secara langsung.

Berikut ini contohnya:

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());
}

Apakah mungkin untuk memodifikasi yang kedua dan mendapatkan hasil yang mirip dengan contoh pertama?

Catatan Tambahan
jangan pernah mengandalkan data dari tugas untuk disusun secara berurutan, kecuali Anda secara eksplisit menerapkan jaminan bahwa item akan kembali berurutan. TaskScheduler memilih Tasks untuk berjalan normal di FIFO(yang pertama masuk pertama keluar), namun, jika TaskScheduler mengoptimalkan, memprioritaskan, atau mengalihkan tugas (yang sering terjadi) tugas Anda tidak akan selesaikan secara berurutan dan selanjutnya hasil yang Anda peroleh tidak akan berurutan.

person DekuDesu    schedule 28.05.2021