Apa yang dilakukan SynchronizationContext?

135

Dalam buku Programming C #, ada beberapa kode contoh tentang SynchronizationContext:

SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
    string text = File.ReadAllText(@"c:\temp\log.txt");
    originalContext.Post(delegate {
        myTextBox.Text = text;
    }, null);
});

Saya pemula di utas, jadi harap jawab dengan detail. Pertama, saya tidak tahu apa artinya konteks, apa yang disimpan dalam program originalContext? Dan ketika Postmetode ini diaktifkan, apa yang akan dilakukan thread UI?
Jika saya menanyakan beberapa hal konyol, tolong perbaiki saya, terima kasih!

EDIT: Misalnya, bagaimana jika saya hanya menulis myTextBox.Text = text;dalam metode ini, apa bedanya?

FannyFan
sumber
1
Manual yang baik mengatakan ini . Tujuan dari model sinkronisasi yang diterapkan oleh kelas ini adalah untuk memungkinkan operasi asinkron / sinkronisasi internal runtime bahasa umum untuk berperilaku baik dengan model sinkronisasi yang berbeda. Model ini juga menyederhanakan beberapa persyaratan yang harus diikuti oleh aplikasi terkelola agar berfungsi dengan benar di bawah lingkungan sinkronisasi yang berbeda.
ta.speot.is
IMHO async menunggu sudah melakukan ini
Royi Namir
7
@RoyiNamir: Ya, tapi coba tebak: asyncSaya awaitmengandalkan di SynchronizationContextbawahnya.
stakx - tidak lagi berkontribusi

Jawaban:

170

Apa yang dilakukan SynchronizationContext?

Sederhananya, SynchronizationContextmerupakan lokasi "di mana" kode dapat dieksekusi. Delegasi yang diteruskan ke metodeSend atau Postmetode akan dipanggil di lokasi itu. ( Postadalah versi non-blocking / asinkron dari Send.)

Setiap utas dapat memiliki SynchronizationContextinstance yang terkait dengannya. Thread yang sedang berjalan dapat dikaitkan dengan konteks sinkronisasi dengan memanggil metode statisSynchronizationContext.SetSynchronizationContext , dan konteks saat ini dari thread yang sedang berjalan dapat ditanyakan melalui SynchronizationContext.Currentproperti .

Terlepas dari apa yang baru saja saya tulis (setiap utas memiliki konteks sinkronisasi terkait), SynchronizationContexttidak selalu mewakili utas tertentu ; itu juga dapat meneruskan permintaan delegasi yang diteruskan ke salah satu dari beberapa utas (misalnya ke ThreadPoolutas pekerja), atau (setidaknya secara teori) ke inti CPU tertentu , atau bahkan ke host jaringan lain . Di mana delegasi Anda berakhir tergantung pada jenis yang SynchronizationContextdigunakan.

Formulir Windows akan menginstal WindowsFormsSynchronizationContextpada utas di mana formulir pertama dibuat. (Utas ini biasa disebut "utas UI".) Jenis konteks sinkronisasi ini memanggil para delegasi yang dilewatinya pada utas tersebut. Ini sangat berguna karena Formulir Windows, seperti banyak kerangka kerja UI lainnya, hanya memungkinkan manipulasi kontrol pada utas yang sama dengan yang mereka buat.

Bagaimana jika saya hanya menulis myTextBox.Text = text;di metode, apa bedanya?

Kode yang telah Anda lewati ThreadPool.QueueUserWorkItemakan dijalankan pada utas pekerja rangkaian utas. Artinya, itu tidak akan dieksekusi pada utas tempat Anda myTextBoxdibuat, jadi Formulir Windows cepat atau lambat (terutama dalam rilis Rilis) melemparkan pengecualian, memberi tahu Anda bahwa Anda tidak dapat mengakses myTextBoxdari seluruh utas lainnya.

Inilah sebabnya mengapa Anda harus "beralih kembali" dari utas pekerja ke "utas UI" (tempat myTextBoxdibuat) sebelum penugasan tertentu. Ini dilakukan sebagai berikut:

  1. Saat Anda masih di utas UI, tangkap Windows Forms 'di SynchronizationContextsana, dan simpan referensi ke sana dalam variabel ( originalContext) untuk digunakan nanti. Anda harus meminta SynchronizationContext.Currentpada titik ini; jika Anda menanyakannya di dalam kode yang diteruskan ke ThreadPool.QueueUserWorkItem, Anda mungkin mendapatkan konteks sinkronisasi apa pun yang dikaitkan dengan utas pekerja rangkaian utas. Setelah Anda menyimpan referensi ke konteks Formulir Windows, Anda dapat menggunakannya di mana saja dan kapan saja untuk "mengirim" kode ke utas UI.

  2. Setiap kali Anda perlu memanipulasi elemen UI (tetapi tidak, atau mungkin tidak, pada utas UI lagi), mengakses konteks sinkronisasi Formulir Windows melalui originalContext, dan menyerahkan kode yang akan memanipulasi UI untuk salah satu Sendatau Post.


Komentar dan petunjuk terakhir:

  • Konteks sinkronisasi apa yang tidak akan dilakukan untuk Anda memberi tahu Anda kode mana yang harus dijalankan di lokasi / konteks tertentu, dan kode mana yang hanya dapat dieksekusi secara normal, tanpa meneruskannya ke a SynchronizationContext. Untuk memutuskan itu, Anda harus mengetahui aturan dan persyaratan kerangka kerja yang Anda pemrogramankan - Formulir Windows dalam kasus ini.

    Jadi, ingat aturan sederhana ini untuk Formulir Windows: JANGAN mengakses kontrol atau formulir dari utas selain dari yang membuatnya. Jika Anda harus melakukan ini, gunakan SynchronizationContextmekanisme seperti dijelaskan di atas, atau Control.BeginInvoke(yang merupakan cara khusus Windows Forms untuk melakukan hal yang persis sama).

  • Jika Anda pemrograman sedang melawan NET 4.5 atau lambat, Anda dapat membuat hidup Anda lebih mudah dengan mengkonversi kode Anda yang secara eksplisit menggunakan SynchronizationContext, ThreadPool.QueueUserWorkItem, control.BeginInvoke, dll ke baru async/ awaitkata kunci dan Paralel Tugas Perpustakaan (TPL) , yaitu API sekitarnya yang Taskdan Task<TResult>kelas. Ini akan, pada tingkat yang sangat tinggi, menjaga menangkap konteks sinkronisasi utas UI, memulai operasi asinkron, kemudian kembali ke utas UI sehingga Anda dapat memproses hasil operasi.

stakx - tidak lagi berkontribusi
sumber
Anda mengatakan Windows Forms, seperti banyak kerangka UI lainnya, hanya memungkinkan manipulasi kontrol pada utas yang sama tetapi semua jendela di Windows harus diakses oleh utas yang sama yang membuatnya.
user34660
4
@ user34660: Tidak, itu tidak benar. Anda dapat memiliki beberapa utas yang membuat kontrol Windows Forms. Tetapi setiap kontrol dikaitkan dengan satu utas yang membuatnya, dan hanya boleh diakses oleh satu utas itu. Kontrol dari utas UI yang berbeda juga sangat terbatas dalam cara mereka berinteraksi satu sama lain: satu tidak dapat menjadi orangtua / anak dari yang lain, pengikatan data di antara mereka tidak mungkin, dll. Akhirnya, setiap utas yang membuat kontrol perlu pesannya sendiri loop (yang dimulai oleh Application.Run, IIRC). Ini adalah topik yang cukup maju dan bukan sesuatu yang dilakukan dengan santai.
stakx - tidak lagi berkontribusi
Komentar pertama saya adalah karena Anda mengatakan "seperti banyak kerangka UI lainnya" yang menyiratkan bahwa beberapa jendela memungkinkan "manipulasi kontrol" dari utas yang berbeda tetapi tidak ada jendela Windows yang melakukannya. Anda tidak dapat "memiliki beberapa utas yang membuat kontrol Windows Forms" untuk jendela yang sama dan "harus diakses oleh utas yang sama" dan "hanya boleh diakses oleh satu utas" yang mengatakan hal yang sama. Saya ragu bahwa adalah mungkin untuk membuat "Kontrol dari utas UI yang berbeda" untuk jendela yang sama. Semua ini tidak maju bagi kita yang berpengalaman dengan pemrograman Windows sebelumnya. Net.
user34660
3
Semua pembicaraan tentang "windows" dan "Windows windows" ini membuat saya agak pusing. Apakah saya menyebutkan salah satu dari "jendela" ini? Saya kira tidak ...
stakx - tidak lagi berkontribusi
1
@ibubi: Saya tidak yakin saya mengerti pertanyaan Anda. Konteks sinkronisasi utas apa pun tidak disetel ( null) atau turunan SynchronizationContext(atau subkelasnya). Inti dari kutipan itu bukanlah apa yang Anda dapatkan, tetapi apa yang tidak akan Anda dapatkan: konteks sinkronisasi utas UI.
stakx - tidak lagi berkontribusi
24

Saya ingin menambahkan jawaban lain, SynchronizationContext.Postcukup mengantre callback untuk eksekusi selanjutnya pada utas target (biasanya selama siklus berikutnya dari loop pesan utas target), dan kemudian eksekusi berlanjut pada utas panggilan. Di sisi lain, SynchronizationContext.Sendmencoba untuk mengeksekusi callback pada utas target segera, yang memblokir utas panggilan dan dapat mengakibatkan kebuntuan. Dalam kedua kasus, ada kemungkinan untuk reentrancy kode (memasukkan metode kelas pada utas eksekusi yang sama sebelum panggilan sebelumnya ke metode yang sama telah kembali).

Jika Anda akrab dengan model pemrograman Win32, analogi sangat dekat akan PostMessagedan SendMessageAPI, yang dapat Anda hubungi untuk mengirimkan pesan dari berbagai benang dari satu jendela target.

Berikut adalah penjelasan yang sangat bagus tentang konteks sinkronisasi itu: Ini Semua Tentang Konteks Sinkronisasi .

noseratio
sumber
16

Ini menyimpan penyedia sinkronisasi, kelas yang berasal dari SynchronizationContext. Dalam hal ini yang mungkin akan menjadi turunan dari WindowsFormsSynchronizationContext. Kelas itu menggunakan metode Control.Invoke () dan Control.BeginInvoke () untuk mengimplementasikan metode Send () dan Post (). Atau bisa juga DispatcherSynchronizationContext, ia menggunakan Dispatcher.Invoke () dan BeginInvoke (). Dalam aplikasi WinForms atau WPF, penyedia itu secara otomatis diinstal segera setelah Anda membuat jendela.

Saat Anda menjalankan kode pada utas lain, seperti utas kumpulan utas yang digunakan dalam cuplikan, maka Anda harus berhati-hati agar Anda tidak langsung menggunakan objek yang tidak aman-utas. Seperti objek antarmuka pengguna apa pun, Anda harus memperbarui properti TextBox.Text dari utas yang membuat TextBox. Metode Post () memastikan bahwa target delegasi berjalan pada utas itu.

Berhati-hatilah bahwa cuplikan ini agak berbahaya, hanya akan berfungsi dengan benar saat Anda memanggilnya dari utas UI. SynchronizationContext.Current memiliki nilai yang berbeda di utas yang berbeda. Hanya utas UI yang memiliki nilai yang dapat digunakan. Dan adalah alasan kode harus menyalinnya. Cara yang lebih mudah dibaca dan lebih aman untuk melakukannya, di aplikasi Winforms:

    ThreadPool.QueueUserWorkItem(delegate {
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.BeginInvoke(new Action(() => {
            myTextBox.Text = text;
        }));
    });

Yang memiliki kelebihan itu berfungsi ketika dipanggil dari utas apa pun . Keuntungan menggunakan SynchronizationContext.Current adalah bahwa ia masih berfungsi apakah kode tersebut digunakan dalam Winforms atau WPF, itu penting di perpustakaan. Ini tentu bukan contoh yang baik dari kode semacam itu, Anda selalu tahu apa jenis TextBox yang Anda miliki di sini sehingga Anda selalu tahu apakah akan menggunakan Control.BeginInvoke atau Dispatcher.BeginInvoke. Sebenarnya menggunakan SynchronizationContext.Current tidak umum.

Buku ini mencoba mengajarkan Anda tentang threading, jadi menggunakan contoh cacat ini tidak masalah. Dalam kehidupan nyata, dalam beberapa kasus di mana Anda mungkin mempertimbangkan untuk menggunakan SynchronizationContext.Current, Anda masih akan menyerahkannya ke async / menunggu kata kunci C # atau TaskScheduler.FromCurrentSynchronizationContext () untuk melakukannya untuk Anda. Tetapi harap dicatat bahwa mereka masih melakukan kesalahan seperti yang dilakukan cuplikan ketika Anda menggunakannya di utas yang salah, untuk alasan yang sama persis. Pertanyaan yang sangat umum di sekitar sini, tingkat abstraksi ekstra berguna tetapi membuatnya lebih sulit untuk mencari tahu mengapa mereka tidak bekerja dengan benar. Semoga buku ini juga memberi tahu Anda kapan tidak menggunakannya :)

Hans Passant
sumber
Maaf, mengapa membiarkan pegangan ulir UI aman? yaitu saya pikir utas UI dapat menggunakan myTextBox ketika Post () dipecat, apakah itu aman?
cloudyFan
4
Bahasa Inggris Anda sulit diterjemahkan. Cuplikan asli Anda hanya berfungsi dengan benar saat dipanggil dari utas UI. Ini adalah kasus yang sangat umum. Baru setelah itu akan memposting kembali ke utas UI. Jika dipanggil dari utas pekerja maka target delegasi Post () akan berjalan pada utas threadpool. Kaboom. Ini adalah sesuatu yang ingin Anda coba sendiri. Mulai utas dan biarkan utas memanggil kode ini. Anda melakukannya dengan benar jika kode mogok dengan NullReferenceException.
Hans Passant
5

Tujuan dari konteks sinkronisasi di sini adalah untuk memastikan bahwa myTextbox.Text = text;dipanggil pada utas UI utama.

Windows mengharuskan kontrol GUI diakses hanya oleh utas yang dibuatnya. Jika Anda mencoba menetapkan teks di utas latar tanpa terlebih dahulu menyinkronkan (melalui salah satu dari beberapa cara, seperti ini atau pola Invoke) maka pengecualian akan dibuang.

Apa yang dilakukan adalah menyimpan konteks sinkronisasi sebelum membuat utas latar belakang, lalu utas latar belakang menggunakan konteks. Metode posting menjalankan kode GUI.

Ya, kode yang Anda tunjukkan pada dasarnya tidak berguna. Mengapa membuat utas latar belakang, hanya untuk segera perlu kembali ke utas UI utama? Itu hanya sebuah contoh.

Erik Funkenbusch
sumber
4
"Ya, kode yang kamu tunjukkan pada dasarnya tidak berguna. Mengapa membuat utas latar, hanya untuk segera perlu kembali ke utas UI utama? Itu hanya sebuah contoh." - Membaca dari file mungkin merupakan tugas yang panjang jika file tersebut besar, sesuatu yang dapat memblokir utas UI dan membuatnya tidak responsif
Yair Nevet
Saya punya pertanyaan bodoh. Setiap utas memiliki ID, dan saya kira utas UI juga memiliki ID = 2 misalnya. Lalu, ketika saya berada di utas thread pool, Dapatkah saya melakukan sesuatu seperti itu: var thread = GetThread (2); thread.Execute (() => textbox1.Text = "foo")?
John
@ John - Tidak, saya pikir itu tidak berhasil karena utas sudah berjalan. Anda tidak dapat menjalankan utas yang sudah dijalankan. Jalankan hanya berfungsi ketika utas tidak berjalan (IIRC)
Erik Funkenbusch
3

Ke Sumber

Setiap utas memiliki konteks yang terkait dengannya - ini juga dikenal sebagai konteks "saat ini" - dan konteks ini dapat dibagi di seluruh utas. The ExecutionContext berisi metadata yang relevan dari lingkungan atau konteks saat ini di mana program sedang dieksekusi. SynchronizationContext mewakili abstraksi - ini menunjukkan lokasi di mana kode aplikasi Anda dieksekusi.

SynchronizationContext memungkinkan Anda untuk mengantri tugas ke konteks lain. Perhatikan bahwa setiap utas dapat memiliki SynchronizatonContext sendiri.

Misalnya: Misalkan Anda memiliki dua utas, Utas 1 dan Utas 2. Katakan, Thread1 melakukan beberapa pekerjaan, dan kemudian Thread1 ingin mengeksekusi kode di Thread2. Salah satu cara yang mungkin untuk melakukannya adalah dengan meminta Thread2 untuk objek SynchronizationContext-nya, memberikannya kepada Thread1, dan kemudian Thread1 dapat memanggil SynchronizationContext. Kirim untuk mengeksekusi kode pada Thread2.

Mata yang besar
sumber
2
Konteks sinkronisasi tidak harus dikaitkan dengan utas tertentu. Dimungkinkan untuk beberapa utas menangani permintaan ke konteks sinkronisasi tunggal, dan utas tunggal untuk menangani permintaan untuk banyak konteks sinkronisasi.
Servy
3

SynchronizationContext memberi kami cara untuk memperbarui UI dari utas yang berbeda (secara sinkron melalui metode Kirim atau secara tidak sinkron melalui metode Posting).

Lihatlah contoh berikut:

    private void SynchronizationContext SyncContext = SynchronizationContext.Current;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(Work1);
        thread.Start(SyncContext);
    }

    private void Work1(object state)
    {
        SynchronizationContext syncContext = state as SynchronizationContext;
        syncContext.Post(UpdateTextBox, syncContext);
    }

    private void UpdateTextBox(object state)
    {
        Thread.Sleep(1000);
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.Text = text;
    }

SynchronizationContext.Current akan mengembalikan konteks sinkronisasi utas UI. Bagaimana saya tahu ini? Di awal setiap formulir atau aplikasi WPF, konteksnya akan ditetapkan pada utas UI. Jika Anda membuat aplikasi WPF dan menjalankan contoh saya, Anda akan melihat bahwa ketika Anda mengklik tombol, itu tidur selama sekitar 1 detik, maka itu akan menampilkan konten file. Anda mungkin berharap itu tidak akan terjadi karena pemanggil metode UpdateTextBox (yang merupakan Work1) adalah metode yang diteruskan ke Thread, oleh karena itu ia harus menggunakan utas tersebut bukan utas UI utama, NOPE! Meskipun metode Work1 diteruskan ke utas, perhatikan bahwa ia juga menerima objek yang merupakan SyncContext. Jika Anda melihatnya, Anda akan melihat bahwa metode UpdateTextBox dijalankan melalui metode syncContext.Post dan bukan metode Work1. Lihatlah yang berikut ini:

private void Button_Click(object sender, RoutedEventArgs e) 
{
    Thread.Sleep(1000);
    string text = File.ReadAllText(@"c:\temp\log.txt");
    myTextBox.Text = text;
}

Contoh terakhir dan yang ini menjalankan hal yang sama. Keduanya tidak memblokir UI saat itu berfungsi.

Sebagai kesimpulan, pikirkan SynchronizationContext sebagai utas. Ini bukan utas, ini menentukan utas (Perhatikan bahwa tidak semua utas memiliki SyncContext). Setiap kali kita memanggil metode Posting atau Kirim untuk memperbarui UI, itu seperti memperbarui UI secara normal dari utas UI utama. Jika, karena alasan tertentu, Anda perlu memperbarui UI dari utas yang berbeda, pastikan utas itu memiliki SyncContext utas utama UI dan panggil metode Kirim atau Posting di atasnya dengan metode yang ingin Anda jalankan dan Anda semua set.

Semoga ini bisa membantu Anda, sobat!

Marc2001
sumber
2

SynchronizationContext pada dasarnya adalah penyedia eksekusi delegasi callback yang terutama bertanggung jawab untuk memastikan bahwa delegasi dijalankan dalam konteks eksekusi yang diberikan setelah bagian kode tertentu (dimasukkan dalam tugas Task .Net TPL) dari suatu program telah menyelesaikan pelaksanaannya.

Dari sudut pandang teknis, SC adalah kelas C # sederhana yang berorientasi untuk mendukung dan menyediakan fungsinya secara khusus untuk objek Pustaka Tugas Paralel.

Setiap aplikasi .Net, kecuali untuk aplikasi konsol, memiliki implementasi khusus dari kelas ini berdasarkan kerangka dasar tertentu, yaitu: WPF, WindowsForm, Asp Net, Silverlight, ecc ..

Pentingnya objek ini terikat pada sinkronisasi antara hasil yang kembali dari eksekusi kode asyncronous dan pelaksanaan kode dependen yang menunggu hasil dari pekerjaan asyncronous itu.

Dan kata "konteks" adalah singkatan dari konteks eksekusi, yaitu konteks eksekusi saat ini di mana kode tunggu akan dieksekusi, yaitu sinkronisasi antara kode async dan kode tunggunya terjadi dalam konteks eksekusi tertentu, sehingga objek ini dinamai SynchronizationContext: itu mewakili konteks eksekusi yang akan menjaga sinkronisasi kode async dan eksekusi kode tunggu .

Ciro Corvino
sumber
1

Contoh ini dari contoh Linqpad dari Joseph Albahari tetapi sangat membantu dalam memahami apa yang dilakukan konteks Sinkronisasi.

void WaitForTwoSecondsAsync (Action continuation)
{
    continuation.Dump();
    var syncContext = AsyncOperationManager.SynchronizationContext;
    new Timer (_ => syncContext.Post (o => continuation(), _)).Change (2000, -1);
}

void Main()
{
    Util.CreateSynchronizationContext();
    ("Waiting on thread " + Thread.CurrentThread.ManagedThreadId).Dump();
    for (int i = 0; i < 10; i++)
        WaitForTwoSecondsAsync (() => ("Done on thread " + Thread.CurrentThread.ManagedThreadId).Dump());
}
loneshark99
sumber