Mengotomatiskan pola kode InvokeRequired

179

Saya telah menyadari betapa seringnya seseorang perlu menulis pola kode berikut dalam kode GUI yang digerakkan oleh event, di mana

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

menjadi:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Ini adalah pola canggung dalam C #, baik untuk diingat, dan untuk mengetik. Adakah yang datang dengan semacam jalan pintas atau konstruksi yang mengotomatisasi ini ke tingkat tertentu? Akan keren jika ada cara untuk melampirkan fungsi ke objek yang melakukan pemeriksaan ini tanpa harus melalui semua pekerjaan tambahan ini, seperti object1.InvokeIfNecessary.visible = truepintasan jenis.

Jawaban sebelumnya telah membahas ketidakpraktisan dari hanya memanggil Invoke () setiap kali, dan bahkan kemudian sintaks Invoke () keduanya tidak efisien dan masih canggung untuk dihadapi.

Jadi, adakah yang tahu cara pintas?

Tom Corelis
sumber
2
Saya bertanya-tanya hal yang sama, tetapi dalam hal Dispatcher WPF. Periksa ().
Taylor Leese
Saya memikirkan saran yang agak gila yang terinspirasi oleh object1.InvokeIfNecessary.Visible = truekalimat Anda ; lihat jawaban saya yang diperbarui dan beri tahu saya apa yang Anda pikirkan.
Dan Tao
1
Tambahkan Cuplikan untuk membantu menerapkan metode yang disarankan oleh Matt Davis: lihat jawaban saya (terlambat tetapi hanya menunjukkan bagaimana untuk pembaca nanti ;-))
Aaron Gage
3
Saya tidak mengerti mengapa Microsoft tidak melakukan apa pun untuk menyederhanakan hal itu di .NET. Membuat delegasi untuk setiap perubahan pada formulir dari utas benar-benar menjengkelkan.
Kamil
@ Kamil Saya sangat setuju! Ini adalah kekhilafan, mengingat keberadaannya di mana-mana. Dalam kerangka itu, hanya menangani threading jika perlu. Tampak jelas.
SteveCinq

Jawaban:

138

Pendekatan Lee dapat disederhanakan lebih lanjut

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

Dan bisa disebut seperti ini

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

Tidak perlu melewati kontrol sebagai parameter ke delegasi. C # secara otomatis membuat penutupan .


PEMBARUAN :

Menurut beberapa poster lain Controldapat digeneralisasi sebagai ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott menunjukkan bahwa tidak seperti Controlyang ISynchronizeInvokeantarmuka membutuhkan sebuah array objek untuk Invokemetode sebagai daftar parameter untuk action.


PEMBARUAN 2

Suntingan yang disarankan oleh Mike de Klerk (lihat komentar dalam cuplikan kode 1 untuk titik sisipan):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

Lihat komentar ToolmakerSteve di bawah ini untuk keprihatinan tentang saran ini.

Olivier Jacot-Descombes
sumber
2
Bukankah lebih baik memilikinya ISynchronizeInvokedaripada memiliki Control? (Kudos to Jon Skeet stackoverflow.com/questions/711408/… )
Odys
@odyodyodys: Poin bagus. Saya tidak tahu ISynchronizeInvoke. Tetapi satu-satunya jenis yang berasal darinya (menurut Reflektor) adalah Control, sehingga adavantage terbatas.
Olivier Jacot-Descombes
3
@ mike-de-petugas, saya khawatir tentang saran Anda untuk ditambahkan while (!control.Visible) ..sleep... Bagi saya yang memiliki bau kode yang buruk, karena berpotensi menunda tanpa batas (mungkin bahkan loop tak terbatas dalam beberapa kasus), dalam kode yang mungkin memiliki penelepon yang tidak mengharapkan penundaan seperti itu (atau bahkan kebuntuan). IMHO, setiap penggunaan Sleepharus menjadi tanggung jawab masing-masing penelepon, ATAU harus di bungkus terpisah yang jelas ditandai sebagai konsekuensinya. IMHO, biasanya akan lebih baik untuk "gagal keras" (pengecualian, untuk menangkap selama pengujian), atau "tidak melakukan apa-apa" jika kontrolnya tidak siap. Komentar?
ToolmakerSteve
1
@ OlivierJacot-Descombes, Alangkah baiknya, jika Anda menjelaskan bagaimana thread.invokerequired bekerja di belakang?
Sudhir.net
1
InvokeRequiredmemberitahu apakah utas panggilan berbeda dari utas yang membuat kontrol. Invokemeneruskan aksi dari utas panggilan ke utas kontrol tempat eksekusi. Ini memastikan bahwa, sebagai contoh, pengendali event klik tidak pernah terganggu.
Olivier Jacot-Descombes
133

Anda dapat menulis metode ekstensi:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

Dan gunakan seperti ini:

object1.InvokeIfRequired(c => { c.Visible = true; });

EDIT: Seperti yang ditunjukkan Simpzon dalam komentar, Anda juga dapat mengubah tanda tangan menjadi:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control
Lee
sumber
Mungkin saya terlalu bodoh, tetapi kode ini tidak dapat dikompilasi. Jadi saya memperbaikinya karena dibangun oleh saya (VS2008).
Oliver
5
Hanya untuk kelengkapan: Di WPF ada mekanisme pengiriman yang berbeda, tetapi berfungsi agak analog. Anda dapat menggunakan metode ekstensi ini di sana: public static void InvokeIfRequired <T> (Target ini, Aksi <T> aActionToExecute) di mana T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.
1
Saya menambahkan jawaban yang sedikit menyederhanakan solusi Lee.
Olivier Jacot-Descombes
Hai, ketika saya menggunakan sesuatu yang serupa, mungkin ada masalah besar yang berasal dari implementasi generik ini. Jika Kontrol Disposing / Disingkirkan, Anda akan mendapatkan ObjectDisposedException.
Offler
1
@Offler - Nah, jika mereka dibuang ke utas lain Anda memiliki masalah sinkronisasi, itu bukan masalah dalam metode ini.
Lee
33

Inilah formulir yang telah saya gunakan di semua kode saya.

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

Saya mendasarkan ini pada entri blog di sini . Saya belum mendapatkan pendekatan ini yang membuat saya gagal, jadi saya tidak melihat alasan untuk menyulitkan kode saya dengan cek InvokeRequiredproperti.

Semoga ini membantu.

Matt Davis
sumber
+1 - Saya menemukan entri blog yang sama dengan yang Anda lakukan, dan berpikir ini adalah pendekatan paling bersih dari semua yang diusulkan
Tom Bushell
3
Ada hit kinerja kecil menggunakan pendekatan ini, yang bisa menumpuk ketika dipanggil beberapa kali. stackoverflow.com/a/747218/724944
surfen
4
Anda harus menggunakan InvokeRequiredjika kode dapat dieksekusi sebelum kontrol ditampilkan atau Anda akan memiliki pengecualian fatal.
56ka
9

Buat file ThreadSafeInvoke.snippet, dan kemudian Anda bisa memilih pernyataan pembaruan, klik kanan dan pilih 'Surround With ...' atau Ctrl-K + S:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippet Format="1.0.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>ThreadsafeInvoke</Title>
    <Shortcut></Shortcut>
    <Description>Wraps code in an anonymous method passed to Invoke for Thread safety.</Description>
    <SnippetTypes>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Code Language="CSharp">
      <![CDATA[
      Invoke( (MethodInvoker) delegate
      {
          $selected$
      });      
      ]]>
    </Code>
  </Snippet>
</CodeSnippet>
Aaron Gage
sumber
6

Berikut ini adalah versi jawaban Lee, Oliver dan Stephan yang ditingkatkan / dikombinasikan.

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

Template memungkinkan untuk kode yang fleksibel dan tanpa cor yang jauh lebih mudah dibaca sementara delegasi khusus memberikan efisiensi.

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});
gxtaillon
sumber
4

Saya lebih suka menggunakan contoh tunggal metode Delegasi daripada membuat contoh baru setiap kali. Dalam kasus saya, saya digunakan untuk menampilkan kemajuan dan (info / kesalahan) pesan dari Backroundworker menyalin dan melemparkan data besar dari contoh sql. Setiap saat setelah sekitar 70000 kemajuan dan panggilan pesan formulir saya berhenti berfungsi dan menampilkan pesan baru. Ini tidak terjadi ketika saya mulai menggunakan delegasi instance global tunggal.

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}
stephan Schmuck
sumber
3

Pemakaian:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

Kode:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}
Konstantin S.
sumber
2

Saya agak suka melakukannya sedikit berbeda, saya suka menyebut "diri saya" jika diperlukan dengan Aksi,

    private void AddRowToListView(ScannerRow row, bool suspend)
    {
        if (IsFormClosing)
            return;

        if (this.InvokeRequired)
        {
            var A = new Action(() => AddRowToListView(row, suspend));
            this.Invoke(A);
            return;
        }
         //as of here the Code is thread-safe

ini adalah pola yang berguna, IsFormClosing adalah bidang yang saya setel ke True ketika saya menutup formulir saya karena mungkin ada beberapa utas latar belakang yang masih berjalan ...

Walter Verhoeven
sumber
-3

Anda tidak boleh menulis kode yang terlihat seperti ini:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

Jika Anda memiliki kode yang terlihat seperti ini maka aplikasi Anda tidak aman-utas. Ini berarti bahwa Anda memiliki kode yang sudah memanggil DoGUISwitch () dari utas yang berbeda. Sudah terlambat untuk memeriksa untuk melihat apakah ada di utas yang berbeda. InvokeRequire harus dipanggil SEBELUM Anda menelepon DoGUISwitch. Anda tidak boleh mengakses metode atau properti apa pun dari utas berbeda.

Referensi: Control.InvokeRequired Property tempat Anda dapat membaca yang berikut ini:

Selain properti InvokeRequired, ada empat metode pada kontrol yang aman untuk dipanggil: Invoke, BeginInvoke, EndInvoke, dan CreateGraphics jika pegangan untuk kontrol telah dibuat.

Dalam arsitektur CPU tunggal tidak ada masalah, tetapi dalam arsitektur multi-CPU Anda dapat menyebabkan bagian dari utas UI ditugaskan ke prosesor tempat kode panggilan dijalankan ... dan jika prosesor itu berbeda dari tempat utas UI sedang berjalan maka ketika utas panggilan berakhir Windows akan berpikir bahwa utas UI telah berakhir dan akan mematikan proses aplikasi yaitu aplikasi Anda akan keluar tanpa kesalahan.

Steve Wood
sumber
Hai, terima kasih atas jawaban Anda. Sudah bertahun-tahun sejak saya mengajukan pertanyaan ini (dan hampir sama lama sejak saya bekerja dengan C #), tapi saya bertanya-tanya apakah Anda bisa menjelaskan sedikit lebih jauh? Docs yang Anda tautkan merujuk pada bahaya panggilan tertentu invoke()et al sebelum kontrol diberi pegangan, tetapi IMHO tidak menjelaskan apa yang telah Anda uraikan. Inti dari semua invoke()omong kosong ini adalah untuk memperbarui UI dengan cara yang aman, dan saya pikir menempatkan lebih banyak instruksi dalam konteks pemblokiran akan menyebabkan kegagapan? (Ugh ... senang saya berhenti menggunakan M $ tech. Sangat rumit!)
Tom Corelis
Saya juga ingin mencatat bahwa meskipun sering menggunakan kode asli (saat kembali), saya tidak mengamati masalah yang Anda jelaskan di desktop dual-CPU
Tom Corelis
3
Saya ragu jawaban ini akurat karena MSDN menunjukkan banyak contoh seperti yang diberikan OP.
nirkabel publik