Operasi lintas-thread tidak valid: Kontrol diakses dari utas selain utas yang dibuatnya

584

Saya punya skenario. (Formulir Windows, C #, .NET)

  1. Ada bentuk utama yang menampung beberapa kontrol pengguna.
  2. Kontrol pengguna melakukan beberapa operasi data berat, sehingga jika saya langsung memanggil UserControl_Load metode, UI menjadi tidak responsif selama durasi untuk eksekusi metode beban.
  3. Untuk mengatasi ini saya memuat data pada utas yang berbeda (mencoba mengubah kode yang ada sesedikit mungkin)
  4. Saya menggunakan utas pekerja latar belakang yang akan memuat data dan ketika selesai akan memberi tahu aplikasi bahwa ia telah melakukan tugasnya.
  5. Sekarang muncul masalah nyata. Semua UI (bentuk utama dan kontrol pengguna anak) dibuat pada utas utama utama. Dalam metode LOAD dari usercontrol saya mengambil data berdasarkan nilai-nilai beberapa kontrol (seperti kotak teks) pada userControl.

Kodesemu akan terlihat seperti ini:

KODE 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

Pengecualian yang diberikannya adalah

Operasi lintas-thread tidak valid: Kontrol diakses dari utas selain utas yang dibuatnya.

Untuk mengetahui lebih banyak tentang ini, saya melakukan beberapa googling dan sebuah saran muncul seperti menggunakan kode berikut

KODE 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

TAPI TAPI TAPI ... sepertinya saya kembali ke titik awal. Aplikasi kembali menjadi tidak responsif. Tampaknya karena eksekusi baris # 1 jika kondisi. Tugas pemuatan sekali lagi dilakukan oleh utas induk dan bukan yang ketiga yang saya hasilkan.

Saya tidak tahu apakah saya menganggap ini benar atau salah. Saya baru mengenal threading.

Bagaimana cara mengatasi ini dan juga apa efek dari eksekusi Baris # 1 jika diblokir?

Situasinya adalah ini : Saya ingin memuat data ke variabel global berdasarkan nilai kontrol. Saya tidak ingin mengubah nilai kontrol dari utas anak. Saya tidak akan pernah melakukannya dari utas anak.

Jadi hanya mengakses nilai sehingga data yang sesuai dapat diambil dari basis data.

Prerak K
sumber
Untuk contoh khusus saya dari kesalahan ini, saya menemukan solusinya adalah menggunakan BackgroundWorker pada formulir untuk menangani bagian kode yang intensif data. (Yaitu menempatkan semua kode masalah ke metode backgroundWorker1_DoWork () dan menyebutnya melalui backgroundWorker1.RunWorkerAsync ()) ... Kedua sumber ini menunjuk saya ke arah yang benar: stackoverflow.com/questions/4806742/… youtube.com/ watch? v = MLrrbG6V1zM
Giollia

Jawaban:

433

Sesuai komentar pembaruan Prerak K (sejak dihapus):

Saya kira saya belum menyajikan pertanyaan dengan benar.

Situasi adalah ini: Saya ingin memuat data ke dalam variabel global berdasarkan nilai kontrol. Saya tidak ingin mengubah nilai kontrol dari utas anak. Saya tidak akan pernah melakukannya dari utas anak.

Jadi hanya mengakses nilai sehingga data yang sesuai dapat diambil dari database.

Solusi yang Anda inginkan akan terlihat seperti:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

Lakukan pemrosesan serius Anda di utas terpisah sebelum Anda mencoba untuk beralih kembali ke utas kontrol. Sebagai contoh:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}
Jeff Hubbard
sumber
1
Sudah lama sekarang sejak saya melakukan pemrograman C #, tetapi berdasarkan artikel MSDN dan pengetahuan saya yang tidak lengkap, sepertinya itu.
Jeff Hubbard
1
Perbedaannya adalah, BeginInvoke () asinkron sedangkan Invoke () berjalan secara sinkron. stackoverflow.com/questions/229554/…
frzsombor
179

Model Threading di UI

Baca Model Threading di aplikasi UI ( tautan VB lama ada di sini ) untuk memahami konsep dasar. Tautan menavigasi ke halaman yang menjelaskan model threading WPF. Namun, Formulir Windows menggunakan ide yang sama.

Utas UI

  • Hanya ada satu utas (utas UI), yang diizinkan untuk mengakses System.Windows.Forms.Control dan anggota subkelasnya.
  • Mencoba mengakses anggota System.Windows.Forms.Control dari utas berbeda dari utas UI akan menyebabkan pengecualian lintas-utas.
  • Karena hanya ada satu utas, semua operasi UI diantrekan sebagai item kerja ke utas itu:

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Metode BeginInvoke dan Invoke

masukkan deskripsi gambar di sini

Memohon

masukkan deskripsi gambar di sini

MulaiInvoke

masukkan deskripsi gambar di sini

Solusi kode

Baca jawaban atas pertanyaan Bagaimana cara memperbarui GUI dari utas lain di C #? . Untuk C # 5.0 dan .NET 4.5 solusi yang disarankan ada di sini .

Ryszard Dżegan
sumber
72

Anda hanya ingin menggunakan Invokeatau BeginInvokeuntuk pekerjaan minimum yang diperlukan untuk mengubah UI. Metode "berat" Anda harus dijalankan pada utas lain (misalnya via BackgroundWorker) tetapi kemudian menggunakan Control.Invoke/ Control.BeginInvokehanya untuk memperbarui UI. Dengan begitu utas UI Anda akan bebas menangani acara UI, dll.

Lihat artikel threading saya untuk contoh WinForms - meskipun artikel itu ditulis sebelum BackgroundWorkertiba di tempat kejadian, dan saya khawatir saya belum memperbaruinya dalam hal itu. BackgroundWorkerhanya menyederhanakan panggilan balik sedikit.

Jon Skeet
sumber
di sini dalam kondisi saya ini. Saya bahkan tidak mengubah UI. Saya hanya accessig nilai saat ini dari thread anak. saran untuk menerapkan
Prerak K
1
Anda masih perlu mengarahkan ke utas UI bahkan hanya untuk mengakses properti. Jika metode Anda tidak dapat melanjutkan sampai nilai diakses, Anda bisa menggunakan delegasi yang mengembalikan nilai. Tapi ya, buka utas UI.
Jon Skeet
Hai Jon, saya percaya Anda sedang mengarahkan saya ke arah yang benar. Ya saya butuh nilai tanpa itu saya tidak bisa melangkah lebih jauh. Tolong bisakah Anda menjelaskan bahwa 'Menggunakan delegasi yang mengembalikan nilai'. Terima kasih
Prerak K
1
Gunakan delegasi seperti Func <string>: string text = textbox1.Invoke ((Func <string>) () => textbox1.Text); (Itu dengan asumsi Anda menggunakan C # 3.0 - Anda bisa menggunakan metode anonim jika tidak.)
Jon Skeet
45

Saya tahu sudah terlambat sekarang. Namun bahkan hari ini jika Anda mengalami kesulitan mengakses kontrol lintas thread? Ini adalah jawaban terpendek hingga tanggal: P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

Ini adalah cara saya mengakses kontrol formulir apa pun dari utas.

Bravo
sumber
1
Ini memberiku Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Saya menyelesaikannya di sini
rupweb
42

Saya mengalami masalah ini dengan FileSystemWatcherdan menemukan bahwa kode berikut menyelesaikan masalah:

fsw.SynchronizingObject = this

Kontrol kemudian menggunakan objek formulir saat ini untuk menangani peristiwa, dan karenanya akan berada di utas yang sama.

Peter C
sumber
2
Ini menghemat daging saya. Dalam VB.NET saya menggunakan.SynchronizingObject = Me
codingcoding
20

Saya menemukan kode check-and-invoke yang perlu dikotori di dalam semua metode yang terkait dengan formulir terlalu verbose dan tidak dibutuhkan. Berikut adalah metode ekstensi sederhana yang memungkinkan Anda melakukannya sepenuhnya:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

Dan kemudian Anda bisa melakukan ini:

textbox1.Invoke(t => t.Text = "A");

Tidak ada lagi main-main - sederhana.

Rob
sumber
apa yang 't' di sini
Rawat
@Rawat tdalam kasus ini akan menjadi textbox1- ini disahkan sebagai argumen
Rob
17

Kontrol di .NET umumnya tidak aman untuk thread. Itu berarti Anda tidak boleh mengakses kontrol dari utas selain dari tempat ia tinggal. Untuk menyiasatinya, Anda perlu mengaktifkan kontrol, yang merupakan upaya sampel kedua Anda.

Namun, dalam kasus Anda semua yang Anda lakukan adalah melewati metode lama berjalan kembali ke utas utama. Tentu saja, itu bukan yang ingin Anda lakukan. Anda perlu memikirkan kembali ini sedikit sehingga semua yang Anda lakukan pada utas utama adalah menetapkan properti cepat di sana-sini.

Joel Coehoorn
sumber
10

Tampilan baru menggunakan Async / Menunggu dan panggilan balik. Anda hanya perlu satu baris kode jika Anda menyimpan metode ekstensi di proyek Anda.

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

Anda dapat menambahkan hal-hal lain ke metode Ekstensi seperti membungkusnya dalam pernyataan Coba / Tangkap, memungkinkan penelepon untuk memberitahukan jenis apa yang harus dikembalikan setelah selesai, pengecualian panggilan balik ke penelepon:

Menambahkan Try Catch, Logging Pengecualian Otomatis dan CallBack

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }
John Peters
sumber
10

Ini bukan cara yang disarankan untuk mengatasi kesalahan ini tetapi Anda dapat menekannya dengan cepat, ini akan melakukan pekerjaan. Saya lebih suka ini untuk prototipe atau demo. Menambahkan

CheckForIllegalCrossThreadCalls = false

dalam Form1()konstruktor.

Özgür
sumber
9

Ikuti cara paling sederhana (menurut saya) untuk memodifikasi objek dari utas lain:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}
Vanderley Maia
sumber
Sederhana! terimakasih
Ali Esmaeili
7

Saya menemukan kebutuhan untuk ini ketika memprogram pengontrol aplikasi iOS-Phone monotouch di proyek prototipe winforms studio visual di luar xamarin stuidio. Lebih suka memprogram di VS lebih dari xamarin studio sebanyak mungkin, saya ingin controller sepenuhnya dipisahkan dari kerangka telepon. Dengan cara ini menerapkan ini untuk kerangka kerja lain seperti Android dan Windows Phone akan jauh lebih mudah untuk penggunaan di masa depan.

Saya ingin solusi di mana GUI dapat menanggapi peristiwa tanpa beban berurusan dengan kode switching threading di balik setiap klik tombol. Pada dasarnya biarkan pengontrol kelas menanganinya untuk menjaga kode klien tetap sederhana. Anda mungkin dapat memiliki banyak acara di GUI di mana seolah-olah Anda bisa menanganinya di satu tempat di kelas akan lebih bersih. Saya bukan ahli multi theading, beri tahu saya jika ini salah.

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

Formulir GUI tidak menyadari controller menjalankan tugas asinkron.

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}
RandallTo
sumber
6

Ini adalah cara alternatif jika objek yang Anda kerjakan tidak memilikinya

(InvokeRequired)

Ini berguna jika Anda bekerja dengan form utama di kelas selain form utama dengan objek yang ada di form utama, tetapi tidak memiliki InvokeRequired

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

Ini berfungsi sama seperti di atas, tetapi ini adalah pendekatan yang berbeda jika Anda tidak memiliki objek dengan invokerequired, tetapi memiliki akses ke MainForm

Ashitakalax
sumber
5

Sepanjang baris yang sama dengan jawaban sebelumnya, tetapi tambahan yang sangat singkat yang Memungkinkan untuk menggunakan semua properti Kontrol tanpa memiliki pengecualian invokasi benang silang.

Metode Penolong

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

Contoh Penggunaan

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}
Mike
sumber
5
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));
Hamid Jolany
sumber
5

Misalnya untuk mendapatkan teks dari Kontrol utas UI:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function
UrsulRosu
sumber
3

Pertanyaan yang sama: bagaimana cara memperbarui gui-dari-thread-in-c lainnya

Dua arah:

  1. Kembalikan nilai dalam e.result dan gunakan untuk menetapkan nilai kotak teks Anda di backgroundWorker_RunWorkerCompleted event

  2. Deklarasikan beberapa variabel untuk menyimpan nilai-nilai semacam ini di kelas yang terpisah (yang akan berfungsi sebagai pemegang data). Buat instance statis dari kelas ini dan Anda dapat mengaksesnya melalui utas apa pun.

Contoh:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}
Saurabh
sumber
2

Cukup gunakan ini:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });
Hasan Shouman
sumber
0

Tindakan y; // dideklarasikan di dalam kelas

label1.Invoke (y = () => label1.Text = "text");

Antonio Leite
sumber
0

Cara sederhana dan dapat digunakan kembali untuk mengatasi masalah ini.

Metode Perpanjangan

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

Contoh Penggunaan

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}
Timothy Macharia
sumber
-3

Ada dua opsi untuk operasi lintas thread.

Control.InvokeRequired Property 

dan yang kedua adalah menggunakan

SynchronizationContext Post Method

Control.InvokeRequired hanya berguna ketika kontrol yang bekerja diwarisi dari kelas Control sementara SynchronizationContext dapat digunakan di mana saja. Beberapa informasi bermanfaat adalah sebagai tautan berikut

UI Pembaruan Lintas Thread | .Bersih

UI Pembaruan Cross Thread menggunakan SynchronizationContext | .Bersih

Nasir Mahmood
sumber