Mana cara paling sederhana untuk memperbarui Label
dari yang lain Thread
?
Saya memiliki
Form
runningthread1
, dan dari situ saya memulai utas lainnya (thread2
).Sementara
thread2
sedang memproses beberapa file saya ingin memperbaruiLabel
diForm
dengan statusthread2
pekerjaan 's.
Bagaimana saya bisa melakukan itu?
c#
.net
multithreading
winforms
user-interface
Kekejaman
sumber
sumber
Jawaban:
Untuk .NET 2.0, berikut ini sedikit kode yang saya tulis yang melakukan persis seperti yang Anda inginkan, dan berfungsi untuk semua properti di
Control
:Sebut saja seperti ini:
Jika Anda menggunakan .NET 3.0 atau di atasnya, Anda bisa menulis ulang metode di atas sebagai metode ekstensi
Control
kelas, yang kemudian akan menyederhanakan panggilan ke:PEMBARUAN 05/10/2010:
Untuk .NET 3.0 Anda harus menggunakan kode ini:
yang menggunakan ekspresi LINQ dan lambda untuk memungkinkan sintaks yang lebih bersih, lebih sederhana dan lebih aman:
Tidak hanya nama properti sekarang diperiksa pada waktu kompilasi, tipe properti juga, jadi tidak mungkin untuk (misalnya) menetapkan nilai string ke properti boolean, dan karenanya menyebabkan pengecualian runtime.
Sayangnya ini tidak menghentikan siapa pun dari melakukan hal-hal bodoh seperti melewati
Control
properti dan nilai orang lain, jadi yang berikut akan dengan senang hati dikompilasi:Oleh karena itu saya menambahkan cek runtime untuk memastikan bahwa properti yang lewat benar-benar milik
Control
metode yang dipanggil. Tidak sempurna, tetapi masih jauh lebih baik daripada versi .NET 2.0.Jika ada yang punya saran lebih lanjut tentang cara meningkatkan kode ini untuk keamanan waktu kompilasi, silakan berkomentar!
sumber
SetControlPropertyThreadSafe(myLabel, "Text", status)
dipanggil dari modul atau kelas atau bentuk lainCara paling sederhana adalah metode anonim yang diteruskan ke
Label.Invoke
:Perhatikan bahwa
Invoke
blok eksekusi sampai selesai - ini adalah kode sinkron. Pertanyaannya tidak menanyakan tentang kode asinkron, tetapi ada banyak konten di Stack Overflow tentang penulisan kode asinkron ketika Anda ingin mempelajarinya.sumber
Menangani pekerjaan panjang
Sejak .NET 4.5 dan C # 5.0 Anda harus menggunakan Pola Asinkron (TAP) berbasis Tugas bersama dengan async - tunggu kata kunci di semua area (termasuk GUI):
alih-alih Asynchronous Programming Model (APM) dan Asynchronous Pattern (EAP) berbasis peristiwa (yang terakhir termasuk Kelas BackgroundWorker ).
Kemudian, solusi yang disarankan untuk pengembangan baru adalah:
Implementasi asinkron dari pengendali event (Ya, itu saja):
Implementasi utas kedua yang memberi tahu utas UI:
Perhatikan yang berikut ini:
Untuk contoh yang lebih jelas, lihat: Masa Depan C #: Hal-hal baik datang kepada mereka yang 'menunggu' oleh Joseph Albahari .
Lihat juga tentang konsep Model Threading UI .
Menangani pengecualian
Cuplikan di bawah ini adalah contoh cara menangani pengecualian dan beralih
Enabled
properti tombol untuk mencegah beberapa klik selama eksekusi latar belakang.sumber
SecondThreadConcern.LongWork()
melempar pengecualian, dapatkah itu ditangkap oleh utas UI? Ini adalah pos yang bagus, btw.Task.Delay(500).Wait()
? Apa gunanya membuat Tugas untuk hanya memblokir utas saat ini? Anda seharusnya tidak pernah memblokir utas utas!Variasi solusi paling sederhana dari Marc Gravell untuk .NET 4:
Atau gunakan delegasi Tindakan sebagai gantinya:
Lihat di sini untuk perbandingan keduanya: MethodInvoker vs Action for Control.BeginInvoke
sumber
this.refresh()
memaksa membatalkan dan mengecat ulang GUI .. jika itu membantu ..Api dan lupakan metode ekstensi untuk .NET 3.5+
Ini dapat dipanggil menggunakan baris kode berikut:
sumber
@this
hanyalah nama variabel, dalam hal ini referensi ke kontrol saat ini memanggil ekstensi. Anda bisa mengubahnya menjadi sumber, atau apa pun yang mengapung perahu Anda. Saya menggunakan@this
, karena mengacu pada 'Kontrol ini' yang memanggil ekstensi dan konsisten (di kepala saya, setidaknya) dengan menggunakan kata kunci 'ini' dalam kode normal (non-ekstensi).OnUIThread
bukanUIThread
.RunOnUiThread
. Tapi itu hanya selera pribadi.Ini adalah cara klasik yang harus Anda lakukan ini:
Utas pekerja Anda memiliki acara. Utas UI Anda memulai utas lainnya untuk melakukan pekerjaan dan menghubungkan acara pekerja tersebut sehingga Anda dapat menampilkan status utas pekerja.
Kemudian di UI Anda harus memotong utas untuk mengubah kontrol yang sebenarnya ... seperti label atau bilah kemajuan.
sumber
Solusi sederhana adalah menggunakan
Control.Invoke
.sumber
Kode Threading sering bermasalah dan selalu sulit untuk diuji. Anda tidak perlu menulis kode threading untuk memperbarui antarmuka pengguna dari tugas latar belakang. Cukup gunakan kelas BackgroundWorker untuk menjalankan tugas dan metode ReportProgress untuk memperbarui antarmuka pengguna. Biasanya, Anda hanya melaporkan persentase selesai, tetapi ada kelebihan lain yang mencakup objek negara. Berikut ini contoh yang baru saja melaporkan objek string:
Tidak masalah jika Anda selalu ingin memperbarui bidang yang sama. Jika Anda memiliki pembaruan yang lebih rumit untuk dibuat, Anda bisa mendefinisikan kelas untuk mewakili negara UI dan meneruskannya ke metode ReportProgress.
Satu hal terakhir, pastikan untuk mengatur
WorkerReportsProgress
bendera, atauReportProgress
metode ini akan diabaikan sepenuhnya.sumber
backgroundWorker1_RunWorkerCompleted
.Sebagian besar jawaban menggunakan
Control.Invoke
yang merupakan kondisi lomba yang menunggu untuk terjadi . Misalnya, pertimbangkan jawaban yang diterima:Jika pengguna menutup formulir sebelum
this.Invoke
dipanggil (ingat,this
adalahForm
objek),ObjectDisposedException
kemungkinan akan dipecat.Solusinya adalah menggunakan
SynchronizationContext
, khususnyaSynchronizationContext.Current
seperti yang disarankan hamilton.danielb (jawaban lain bergantung padaSynchronizationContext
implementasi spesifik yang sama sekali tidak perlu). Saya akan sedikit memodifikasi kodenya untuk digunakanSynchronizationContext.Post
daripadaSynchronizationContext.Send
meskipun (karena biasanya tidak perlu utas pekerja untuk menunggu):Perhatikan bahwa pada .NET 4.0 dan yang lebih tinggi, Anda harus benar-benar menggunakan tugas untuk operasi async. Lihat jawaban n-san untuk pendekatan berbasis tugas yang setara (menggunakan
TaskScheduler.FromCurrentSynchronizationContext
).Akhirnya, pada .NET 4.5 dan yang lebih tinggi Anda juga dapat menggunakan
Progress<T>
(yang pada dasarnya menangkapSynchronizationContext.Current
saat pembuatannya) seperti yang ditunjukkan oleh Ryszard Dżegan untuk kasus-kasus di mana operasi jangka panjang perlu menjalankan kode UI saat masih bekerja.sumber
Anda harus memastikan bahwa pembaruan terjadi pada utas yang benar; utas UI.
Untuk melakukan ini, Anda harus Meminta pengatur acara alih-alih memanggilnya secara langsung.
Anda dapat melakukan ini dengan meningkatkan acara Anda seperti ini:
(Kode ini diketikkan di sini dari kepala saya, jadi saya belum memeriksa sintaks yang benar, dll., Tetapi itu akan membuat Anda pergi.)
Perhatikan bahwa kode di atas tidak akan berfungsi pada proyek WPF, karena kontrol WPF tidak mengimplementasikan
ISynchronizeInvoke
antarmuka.Untuk memastikan bahwa kode di atas berfungsi dengan Windows Forms dan WPF, dan semua platform lainnya, Anda dapat melihat pada
AsyncOperation
,AsyncOperationManager
danSynchronizationContext
kelas.Untuk meningkatkan acara dengan mudah seperti ini, saya telah membuat metode ekstensi, yang memungkinkan saya untuk menyederhanakan acara dengan hanya menelepon:
Tentu saja, Anda juga dapat menggunakan kelas BackGroundWorker, yang akan mengabstraksi masalah ini untuk Anda.
sumber
Anda harus mengaktifkan metode pada utas GUI. Anda dapat melakukannya dengan memanggil Control.Invoke.
Sebagai contoh:
sumber
Karena sepele dari skenario saya benar-benar akan memiliki polling UI untuk statusnya. Saya pikir Anda akan menemukan bahwa itu bisa sangat elegan.
Pendekatan ini menghindari operasi marshaling yang diperlukan saat menggunakan
ISynchronizeInvoke.Invoke
danISynchronizeInvoke.BeginInvoke
metode. Tidak ada yang salah dengan menggunakan teknik marshaling, tetapi ada beberapa peringatan yang perlu Anda waspadai.BeginInvoke
terlalu sering atau itu bisa membanjiri pompa pesan.Invoke
utas pekerja adalah panggilan pemblokiran. Ini akan menghentikan sementara pekerjaan yang dilakukan di utas itu.Strategi yang saya usulkan dalam jawaban ini membalikkan peran komunikasi utas. Alih-alih pekerja thread mendorong data jajak pendapat UI untuk itu. Ini adalah pola umum yang digunakan dalam banyak skenario. Karena semua yang ingin Anda lakukan adalah menampilkan informasi kemajuan dari utas pekerja maka saya pikir Anda akan menemukan bahwa solusi ini adalah alternatif yang bagus untuk solusi marshaling. Ini memiliki keuntungan sebagai berikut.
Control.Invoke
atauControl.BeginInvoke
pendekatan yang secara ketat memasangkannya.sumber
Elapsed
acara, Anda menggunakan metode anggota sehingga Anda dapat menghapus timer ketika formulir dibuang ...System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };
dan menugaskannyam_Timer.Elapsed += handler;
, kemudian dalam konteks buang melakukan apam_Timer.Elapsed -= handler;
aku benar? Dan untuk pembuangan / penutupan mengikuti saran seperti yang dibahas di sini .Tidak ada satu pun barang Invoke dalam jawaban sebelumnya yang diperlukan.
Anda perlu melihat WindowsFormsSynchronizationContext:
sumber
Yang ini mirip dengan solusi di atas menggunakan .NET Framework 3.0, tetapi itu memecahkan masalah dukungan keamanan waktu kompilasi .
Menggunakan:
Kompiler akan gagal jika pengguna melewati tipe data yang salah.
sumber
Selamatkan! Setelah mencari pertanyaan ini, saya menemukan jawaban oleh FrankG dan Oregon Ghost sebagai yang termudah yang paling berguna bagi saya. Sekarang, saya kode dalam Visual Basic dan menjalankan cuplikan ini melalui konverter; jadi saya tidak yakin bagaimana hasilnya.
Saya memiliki bentuk dialog yang disebut
form_Diagnostics,
yang memiliki kotak richtext, yang disebutupdateDiagWindow,
yang saya gunakan sebagai semacam tampilan logging. Saya harus dapat memperbarui teksnya dari semua utas. Baris tambahan memungkinkan jendela untuk secara otomatis menggulir ke baris terbaru.Jadi, saya sekarang dapat memperbarui tampilan dengan satu baris, dari mana saja di seluruh program dengan cara yang menurut Anda akan berfungsi tanpa threading:
Kode Utama (letakkan ini di dalam kode kelas formulir Anda):
sumber
Untuk banyak tujuan, sesederhana ini:
"serviceGUI ()" adalah metode level GUI dalam formulir (ini) yang dapat mengubah kontrol sebanyak yang Anda inginkan. Panggil "updateGUI ()" dari utas lainnya. Parameter dapat ditambahkan untuk memberikan nilai, atau (mungkin lebih cepat) menggunakan variabel lingkup kelas dengan kunci yang diperlukan jika ada kemungkinan bentrokan antara thread mengaksesnya yang dapat menyebabkan ketidakstabilan. Gunakan BeginInvoke sebagai ganti Invoke jika utas non-GUI sangat menentukan waktu (ingatlah peringatan Brian Gideon).
sumber
Ini dalam variasi C # 3.0 dari solusi Ian Kemp saya:
Anda menyebutnya seperti ini:
Kalau tidak, yang asli adalah solusi yang sangat bagus.
sumber
Catatan yang
BeginInvoke()
lebih disukai daripadaInvoke()
karena cenderung menyebabkan kebuntuan (namun, ini bukan masalah di sini ketika hanya menetapkan teks ke label):Saat menggunakan
Invoke()
Anda sedang menunggu metode untuk kembali. Sekarang, mungkin Anda melakukan sesuatu dalam kode yang dipanggil yang perlu menunggu utasnya, yang mungkin tidak segera jelas jika itu terkubur dalam beberapa fungsi yang Anda panggil, yang itu sendiri dapat terjadi secara tidak langsung melalui event handler. Jadi Anda akan menunggu utasnya, utas itu akan menunggu Anda dan Anda menemui jalan buntu.Ini sebenarnya menyebabkan beberapa perangkat lunak kami dirilis untuk menggantung. Itu cukup mudah untuk memperbaiki dengan mengganti
Invoke()
denganBeginInvoke()
. Kecuali Anda memiliki kebutuhan untuk operasi sinkron, yang mungkin terjadi jika Anda membutuhkan nilai balik, gunakanBeginInvoke()
.sumber
Ketika saya menemukan masalah yang sama, saya mencari bantuan dari Google, tetapi alih-alih memberi saya solusi sederhana, itu lebih membingungkan saya dengan memberikan contoh
MethodInvoker
dan bla bla bla. Jadi saya memutuskan untuk menyelesaikannya sendiri. Inilah solusi saya:Buat delegasi seperti ini:
Anda dapat memanggil fungsi ini di utas baru seperti ini
Jangan bingung dengan
Thread(() => .....)
. Saya menggunakan fungsi anonim atau ekspresi lambda ketika saya mengerjakan utas. Untuk mengurangi baris kode Anda dapat menggunakanThreadStart(..)
metode yang juga tidak seharusnya saya jelaskan di sini.sumber
Cukup gunakan sesuatu seperti ini:
sumber
e.ProgressPercentage
, bukankah Anda sudah berada di utas UI dari metode yang Anda panggil ini?Anda dapat menggunakan delegasi yang sudah ada
Action
:sumber
Versi saya adalah memasukkan satu baris "mantra" rekursif:
Tanpa argumen:
Untuk fungsi yang memiliki argumen:
ITULAH ITU .
Beberapa argumentasi : Biasanya buruk untuk keterbacaan kode untuk menempatkan {} setelah suatu
if ()
pernyataan dalam satu baris. Tetapi dalam hal ini "mantra" semua-sama-rutin. Itu tidak merusak pembacaan kode jika metode ini konsisten terhadap proyek. Dan itu menyimpan kode Anda dari sampah sembarangan (satu baris kode, bukan lima).Seperti yang Anda lihat,
if(InvokeRequired) {something long}
Anda tahu "fungsi ini aman untuk dipanggil dari utas lainnya".sumber
Coba segarkan label menggunakan ini
sumber
Buat variabel kelas:
Atur di konstruktor yang membuat UI Anda:
Saat Anda ingin memperbarui label:
sumber
Anda harus menggunakan invoke dan delegate
sumber
Sebagian besar jawaban lain sedikit rumit bagi saya untuk pertanyaan ini (saya baru di C #), jadi saya menulis milik saya:
Saya memiliki aplikasi WPF dan telah mendefinisikan pekerja seperti di bawah ini:
Isu:
Larutan:
Saya belum mencari tahu apa arti kalimat di atas, tetapi berhasil.
Untuk WinForms :
Larutan:
sumber
Cara termudah yang saya pikirkan:
sumber
Misalnya, akses kontrol selain dari utas saat ini:
Ada
lblThreshold
Label danSpeed_Threshold
variabel global.sumber
Saat Anda berada di utas UI, Anda dapat meminta penjadwalan tugas konteks sinkronisasi. Ini akan memberi Anda TaskScheduler yang menjadwalkan semuanya di utas UI.
Kemudian, Anda dapat mengaitkan tugas Anda sehingga saat hasilnya siap maka tugas lain (yang dijadwalkan pada utas UI) mengambilnya dan menetapkannya ke label.
Ini berfungsi untuk tugas (bukan utas) yang merupakan cara yang disukai untuk menulis kode bersamaan sekarang .
sumber
Task.Start
biasanya bukan praktik yang baik blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspxSaya baru saja membaca jawabannya dan ini tampaknya menjadi topik yang sangat panas. Saya saat ini menggunakan .NET 3.5 SP1 dan Windows Forms.
Rumus terkenal sangat dijelaskan dalam jawaban sebelumnya yang menggunakan InvokeRequired properti mencakup sebagian besar kasus, tetapi tidak seluruh kumpulan.
Bagaimana jika Handel belum dibuat?
The InvokeRequired properti, seperti yang dijelaskan di sini (referensi Control.InvokeRequired Properti untuk MSDN) mengembalikan nilai true jika panggilan itu dibuat dari benang yang tidak thread GUI, palsu baik jika panggilan itu dibuat dari benang GUI, atau jika Handle adalah belum dibuat.
Anda dapat menemukan pengecualian jika Anda ingin memiliki formulir modal ditampilkan dan diperbarui oleh utas lainnya. Karena Anda ingin formulir itu ditampilkan secara kecil, Anda dapat melakukan hal berikut:
Dan delegasi dapat memperbarui Label pada GUI:
Hal ini dapat menyebabkan InvalidOperationException jika operasi sebelum update label "mengambil sedikit waktu" (membacanya dan menafsirkannya sebagai penyederhanaan) dari waktu yang dibutuhkan untuk thread GUI untuk membuat Form 's Handle . Ini terjadi dalam metode ShowDialog () .
Anda juga harus memeriksa untuk Handle seperti ini:
Anda dapat menangani operasi untuk melakukan jika Menangani belum dibuat: Anda bisa mengabaikan pembaruan GUI (seperti yang ditunjukkan pada kode di atas) atau Anda bisa menunggu (lebih berisiko). Ini harus menjawab pertanyaan.
Hal-hal opsional: Secara pribadi saya membuat kode berikut:
Saya memberi makan formulir saya yang diperbarui oleh utas lain dengan turunan dari ThreadSafeGuiCommand ini , dan saya mendefinisikan metode yang memperbarui GUI (dalam Formulir saya) seperti ini:
Dengan cara ini saya cukup yakin bahwa saya akan memperbarui GUI saya, utas apa pun yang akan melakukan panggilan, secara opsional menunggu jumlah waktu yang ditentukan dengan baik (batas waktu).
sumber