Bagaimana dukungan asinkron C # 5 akan membantu masalah sinkronisasi utas UI?

16

Saya mendengar di suatu tempat bahwa menunggu async C # 5 akan sangat luar biasa sehingga Anda tidak perlu khawatir melakukan hal ini:

if (InvokeRequired)
{
    BeginInvoke(...);
    return;
}
// do your stuff here

Sepertinya panggilan balik dari operasi menunggu akan terjadi di utas asli si penelepon. Telah dikemukakan beberapa kali oleh Eric Lippert dan Anders Hejlsberg bahwa fitur ini berasal dari kebutuhan untuk membuat UI (terutama UI perangkat sentuh) lebih responsif.

Saya pikir penggunaan umum fitur tersebut akan menjadi sesuatu seperti ini:

public class Form1 : Form
{
    // ...
    async void GetFurtherInfo()
    {
        var temperature = await GetCurrentTemperatureAsync();
        label1.Text = temperature;
    }
}

Jika hanya panggilan balik yang digunakan maka pengaturan teks label akan memunculkan pengecualian karena itu tidak dijalankan di utas UI.

Sejauh ini saya tidak dapat menemukan sumber daya yang mengkonfirmasi hal ini. Adakah yang tahu tentang ini? Apakah ada dokumen yang menjelaskan secara teknis bagaimana ini akan bekerja?

Harap berikan tautan dari sumber yang dapat dipercaya, jangan hanya menjawab "ya".

Alex
sumber
Ini tampaknya sangat tidak mungkin, setidaknya sejauh menyangkut awaitfungsi. Ini hanya banyak gula sintaksis untuk kelanjutan lewat . Mungkin ada beberapa perbaikan lain yang tidak terkait dengan WinForms yang seharusnya membantu? Itu akan jatuh di bawah kerangka NET. Itu sendiri, meskipun, dan bukan C # khusus.
Aaronaught
@Aonaonaught, saya setuju, inilah mengapa saya mengajukan pertanyaan dengan tepat. Saya mengedit pertanyaan untuk menjelaskan dari mana saya berasal. Kedengarannya aneh bahwa mereka akan membuat fitur ini dan masih mengharuskan kami menggunakan gaya kode InvokeRequired yang terkenal.
Alex

Jawaban:

17

Saya pikir Anda membuat beberapa hal bingung, di sini. Apa yang Anda minta sudah mungkin digunakan System.Threading.Tasks, asyncdan awaitpada C # 5 hanya akan memberikan sedikit gula sintaksis yang lebih baik untuk fitur yang sama.

Mari kita gunakan contoh Winforms - jatuhkan tombol dan kotak teks di formulir dan gunakan kode ini:

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => DelayedAdd(5, 10))
        .ContinueWith(t => DelayedAdd(t.Result, 20))
        .ContinueWith(t => DelayedAdd(t.Result, 30))
        .ContinueWith(t => DelayedAdd(t.Result, 50))
        .ContinueWith(t => textBox1.Text = t.Result.ToString(),
            TaskScheduler.FromCurrentSynchronizationContext());
}

private int DelayedAdd(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Jalankan dan Anda akan melihat bahwa (a) tidak memblokir utas UI dan (b) Anda tidak mendapatkan kesalahan "operasi lintas-benang yang tidak sah" seperti biasa - kecuali Anda menghapus TaskSchedulerargumen dari yang terakhir ContinueWith, di yang mana Anda akan.

Ini adalah gaya passing kelanjutan standar-rawa . Keajaiban terjadi di TaskSchedulerkelas dan khususnya instance diambil oleh FromCurrentSynchronizationContext. Lewati ini ke kelanjutan apa pun dan Anda memberi tahu bahwa kelanjutan harus berjalan pada utas apa pun yang disebut FromCurrentSynchronizationContextmetode - dalam hal ini, utas UI.

Penunggu sedikit lebih canggih dalam arti bahwa mereka menyadari utas mana yang mereka mulai dan utas mana kelanjutan yang perlu terjadi. Jadi kode di atas dapat ditulis sedikit lebih alami:

private async void button1_Click(object sender, EventArgs e)
{
    int a = await DelayedAddAsync(5, 10);
    int b = await DelayedAddAsync(a, 20);
    int c = await DelayedAddAsync(b, 30);
    int d = await DelayedAddAsync(c, 50);
    textBox1.Text = d.ToString();
}

private async Task<int> DelayedAddAsync(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Kedua harus terlihat sangat mirip, dan pada kenyataannya mereka yang sangat mirip. The DelayedAddAsyncMetode sekarang mengembalikan Task<int>bukan sebuah int, sehingga awaithanya menampar lanjutan ke setiap salah satu dari mereka. Perbedaan utama adalah bahwa ia meneruskan konteks sinkronisasi pada setiap baris, jadi Anda tidak harus melakukannya secara eksplisit seperti yang kami lakukan pada contoh terakhir.

Secara teori perbedaannya jauh lebih signifikan. Dalam contoh kedua, setiap baris tunggal dalam button1_Clickmetode ini sebenarnya dieksekusi di utas UI, tetapi tugas itu sendiri ( DelayedAddAsync) berjalan di latar belakang. Pada contoh pertama, semuanya berjalan di latar belakang , kecuali untuk penugasan textBox1.Textyang telah kami lampirkan secara eksplisit ke konteks sinkronisasi utas UI.

Itulah yang benar-benar menarik await- fakta bahwa seorang penunggu dapat masuk dan keluar dari metode yang sama tanpa memblokir panggilan. Anda panggil await, utas saat ini kembali ke pemrosesan pesan, dan ketika selesai, penunggu akan mengambil tepat di mana itu tinggalkan, di utas yang sama ditinggalkan di. Tapi dalam hal Anda Invoke/ BeginInvokekontras dalam pertanyaan, saya Saya menyesal mengatakan bahwa Anda seharusnya sudah berhenti melakukan itu sejak lama.

Aaronaught
sumber
@Aaronaught sangat menarik. Saya menyadari gaya kelanjutan passing tetapi tidak menyadari hal "konteks sinkronisasi" ini. Apakah ada dokumen yang menghubungkan konteks sinkronisasi ini dengan C # 5 async-wait? Saya mengerti ini adalah fitur yang sudah ada tetapi fakta bahwa mereka menggunakannya secara default terdengar seperti masalah besar, khususnya karena harus memiliki beberapa dampak besar dalam kinerja, kan? Ada komentar lebih lanjut tentang itu? Omong-omong, terima kasih atas jawaban Anda.
Alex
1
@Alex: Untuk jawaban atas semua pertanyaan tindak lanjut tersebut, saya sarankan Anda membaca Kinerja Async: Memahami Biaya Async dan Menunggu . Bagian "Peduli Tentang Konteks" menjelaskan bagaimana semua itu terkait dengan konteks sinkronisasi.
Aaronaught
(Omong-omong, konteks sinkronisasi bukanlah hal baru; mereka sudah ada dalam kerangka kerja sejak 2.0. TPL hanya membuatnya jauh lebih mudah digunakan.)
Aaronaught
2
Saya bertanya-tanya mengapa masih banyak diskusi tentang penggunaan gaya InvokeRequired dan sebagian besar utas yang saya lihat bahkan tidak menyebutkan konteks sinkronisasi. Itu akan menyelamatkan saya waktu untuk mengajukan pertanyaan ini ...
Alex
2
@ Alex: Saya kira Anda tidak mencari di tempat yang tepat . Saya tidak tahu harus mengatakan apa kepada Anda; ada sebagian besar komunitas .NET yang membutuhkan waktu lama untuk mengejar ketinggalan. Sial, saya masih melihat beberapa coders menggunakan ArrayListkelas dalam kode baru. Saya masih memiliki pengalaman dengan RX, saya sendiri. Orang-orang mempelajari apa yang perlu mereka ketahui dan membagikan apa yang sudah mereka ketahui, bahkan jika apa yang sudah mereka ketahui sudah ketinggalan zaman. Jawaban ini mungkin kedaluwarsa dalam beberapa tahun.
Aaronaught
4

Ya, untuk utas UI, panggilan balik dari operasi tunggu akan terjadi pada utas asli si penelepon.

Eric Lippert menulis seri 8-bagian tentang hal itu setahun yang lalu: Fabulous Adventures In Coding

Sunting: dan inilah Anders // build / presentasi: channel9

BTW, apakah Anda memperhatikan bahwa jika Anda membalik "// build /" terbalik, Anda mendapatkan "/ plinq //" ;-)

Nicholas Butler
sumber