Çapraz iş parçacığı işlemi geçerli değil: Denetim, oluşturulduğu iş parçacığı dışındaki bir iş parçacığından erişildi


584

Bir senaryom var. (Windows Forms, C #, .NET)

  1. Bazı kullanıcı kontrollerini barındıran bir ana form vardır.
  2. Kullanıcı denetimi bazı ağır veri işlemi yapar, böylece UserControl_Loadyöntemi doğrudan çağırırsam kullanıcı arabirimi, yükleme yöntemi yürütme süresi boyunca yanıt vermez hale gelir.
  3. Bunun üstesinden gelmek için farklı bir iş parçacığına veri yüklüyorum (mevcut kodu olabildiğince az değiştirmeye çalışıyorum)
  4. Verileri yükleyecek bir arka plan iş parçacığı kullandım ve bittiğinde uygulama işini yaptığını bildirir.
  5. Şimdi gerçek bir sorun geldi. Tüm kullanıcı arayüzü (ana form ve alt kullanıcı kontrolleri) birincil ana iş parçacığında oluşturuldu. Usercontrol LOAD yönteminde userControl bazı denetim (metin kutusu gibi) değerlerine dayalı veri getiriyorum.

Sahte kod şöyle görünecektir:

KOD 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.
    }
}

Verdiği İstisna

Çapraz iş parçacığı işlemi geçerli değil: Denetim, oluşturulduğu iş parçacığı dışındaki bir iş parçacığından erişildi.

Bu konuda daha fazla bilgi edinmek için biraz googling yaptım ve aşağıdaki kodu kullanarak bir öneri geldi

KOD 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
    }
}

ANCAK ANCAK ANCAK ... Görünüşe göre bir kareye geri döndüm. Uygulama tekrar yanıt vermiyor. Durum 1 ise satır 1'in yürütülmesinden kaynaklanıyor gibi görünüyor. Yükleme görevi tekrar doğurduğum üçüncü değil, ana iş parçacığı tarafından yapılır.

Bunu doğru mu yanlış mı algıladığımı bilmiyorum. İplik takmada yeniyim.

Bunu nasıl çözebilirim ve ayrıca eğer blok # 1'in blok halinde yürütülmesinin etkisi nedir?

Durum şudur : Bir denetimin değerine göre küresel bir değişkene veri yüklemek istiyorum. Alt iş parçacığından bir denetimin değerini değiştirmek istemiyorum. Bunu hiç bir çocuk evresinden yapmayacağım.

Bu nedenle, değere yalnızca ilgili verilerin veritabanından getirilebilmesi için erişilir.


Bu hatanın özel örneği için, kodun yoğun veri içeren bölümlerini işlemek için bir BackgroundWorker kullanmak için geçici çözüm buldum. (yani tüm sorun kodunu backgroundWorker1_DoWork () yöntemine koyun ve backgroundWorker1.RunWorkerAsync () yoluyla çağırın) ... Bu iki kaynak beni doğru yönde gösterdi: stackoverflow.com/questions/4806742/… youtube.com/ ? v = MLrrbG6V1zM
Giollia

Yanıtlar:


433

Gereğince Prerak K'nın güncelleme comment (silindi beri):

Sanırım soruyu doğru bir şekilde sunmadım.

Durum şudur: Bir denetimin değerine dayalı olarak global bir değişkene veri yüklemek istiyorum. Alt iş parçacığından bir denetimin değerini değiştirmek istemiyorum. Bunu bir çocuk evresinden asla yapmayacağım.

Bu nedenle, değere yalnızca ilgili verilerin veritabanından getirilebilmesi için erişilir.

İstediğiniz çözüm şöyle görünmelidir:

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

Denetimin iş parçacığına geri dönmeye çalışmadan önce ciddi işlemlerinizi ayrı bir iş parçacığında yapın. Örneğin:

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;
        }
    }
}

1
C # programlama yaptığımdan beri bir süredir oldu, ancak MSDN makalesine ve yama bilgime dayanarak, öyle görünüyor.
Jeff Hubbard

1
Aradaki fark, Invoke () senkronize olarak çalışırken BeginInvoke () zaman uyumsuzdur. stackoverflow.com/questions/229554/…
frzsombor

178

Kullanıcı arayüzünde diş açma modeli

Temel kavramları anlamak için lütfen kullanıcı arayüzü uygulamalarındaki Diş Açma Modeli'ni okuyun . Bağlantı, WPF iş parçacığı modelini açıklayan sayfaya gider. Ancak, Windows Forms da aynı fikri kullanır.

UI İş Parçacığı

  • System.Windows.Forms.Control ve alt sınıf üyelerine erişmesine izin verilen yalnızca bir iş parçacığı (UI iş parçacığı) vardır .
  • UI iş parçacığından farklı iş parçacığından System.Windows.Forms.Control üyesine erişmeye çalışmak, çapraz iş parçacığı özel durumuna neden olur.
  • Yalnızca bir iş parçacığı olduğundan, tüm kullanıcı arabirimi işlemleri bu iş parçacığında iş öğeleri olarak sıraya alınır:

resim açıklamasını buraya girin

resim açıklamasını buraya girin

BeginInvoke ve Invoke yöntemleri

  • UI iş parçacığı orada kullanıldığından, çağrılan yöntemin bilgi işlem yükünün yanı sıra olay işleyici yöntemlerinin de bilgi işlem yükünün küçük olması gerekir - kullanıcı girişini işlemekten sorumludur. Bunun System.Windows.Forms.Control.Invoke veya System.Windows.Forms.Control.BeginInvoke olup olmadığına bakılmaksızın .
  • Hesaplamalı pahalı işlem gerçekleştirmek için her zaman ayrı bir iş parçacığı kullanın. .NET 2.0'dan beri BackgroundWorker , Windows Forms'da pahalı işlemlerin gerçekleştirilmesine adanmıştır. Ancak yeni çözümlerde, burada açıklandığı gibi asenkron beklemeyi kullanmalısınız .
  • System.Windows.Forms.Control.Invoke veya System.Windows.Forms.Control.BeginInvoke yöntemlerini yalnızca bir kullanıcı arabirimini güncelleştirmek için kullanın . Bunları ağır hesaplamalar için kullanırsanız, uygulamanız şunları engeller:

resim açıklamasını buraya girin

Çağırmak

resim açıklamasını buraya girin

Beginınvoke

resim açıklamasını buraya girin

Kod çözümü

Soruya verilen cevapları okuyun GUI C # başka bir iş parçacığından nasıl güncelleştirilir? . C # 5.0 ve .NET 4.5 için önerilen çözüm burada .



72

Yalnızca kullanıcı arayüzünü değiştirmek için gereken minimum iş miktarını Invokeveya BeginInvokeçıplak işi kullanmak istiyorsunuz . "Ağır" yönteminiz başka bir iş parçacığında (örn. Yoluyla BackgroundWorker) yürütülmeli, ancak daha sonra kullanıcı arayüzünü güncellemek için Control.Invoke/ Control.BeginInvokejust komutunu kullanmalıdır. Bu şekilde UI iş parçacığınızda UI olayları vb. İşlenebilir.

Benim Bkz parçacığı makaleye bir için WinForms örnek makale önce yazılmış olmasına rağmen - BackgroundWorkerolay yerine gelmiş ve ben bu açıdan onu güncellemediğinizden korkuyorum. BackgroundWorkersadece geri aramayı biraz basitleştirir.


burada bu durumumda. Kullanıcı arayüzünü değiştirmiyorum bile. Sadece çocuk iş parçacığından geçerli değerlerine erişiyorum. uygulamak için herhangi bir öneri hw
Prerak K

1
Yalnızca özelliklere erişmek için bile UI iş parçacığına marshal yapmanız gerekir. Yönteme değer erişilene kadar devam edemezse, değeri döndüren bir temsilci kullanabilirsiniz. Ancak evet, UI iş parçacığını kullanın.
Jon Skeet

Merhaba Jon, beni doğru yöne yönlendirdiğine inanıyorum. Evet onsuz değere ihtiyacım var daha ileri gidemem. Lütfen 'Değer döndüren bir delege kullanma' konusunu geliştirebilir misiniz? Teşekkürler
Prerak K

1
Func <string> gibi bir temsilci kullanın: string text = textbox1.Invoke ((Func <string>) () => textbox1.Text); (Bu, C # 3.0 kullandığınızı varsayar - aksi takdirde anonim bir yöntem kullanabilirsiniz.)
Jon Skeet

45

Artık çok geç olduğunu biliyorum. Ancak bugün bile çapraz iplik kontrollerine erişmekte sorun yaşıyorsanız? Bu tarihe kadar olan en kısa cevap: P

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

Bu nasıl bir iş parçacığından herhangi bir form denetimine erişmek.


1
Bu bana verir Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Ben onu çözdüm burada
rupweb

42

Ben ile bu sorunu vardı FileSystemWatcherve aşağıdaki kod sorunu çözüldü bulundu:

fsw.SynchronizingObject = this

Denetim daha sonra olaylarla ilgilenmek için geçerli form nesnesini kullanır ve bu nedenle aynı iş parçacığında olacaktır.


2
Bu pastırmamı kurtardı. .SynchronizingObject = Me
VB.NET'te

20

Ben formları ile ilgili tüm yöntemler içinde çok ayrıntılı ve gereksiz olması için çöp gereken kontrol ve çağır kodunu buluyorum. İşte tamamen ortadan kaldırmanızı sağlayan basit bir uzantı yöntemi:

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);
    }
}

Ve sonra bunu yapabilirsiniz:

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

Artık uğraşmak yok - basit.


burada 't' nedir
Rawat

@Rawat tbu durumda olacak textbox1- bir argüman olarak geçti
Rob

17

.NET'teki denetimler genellikle iş parçacığı için güvenli değildir. Bu, yaşadığı yer dışında bir iş parçacığından bir denetime erişmemeniz gerektiği anlamına gelir. Bunun üstesinden gelmek için, 2. örneğinizin denediği şey olan kontrolü başlatmanız gerekir .

Ancak, sizin durumunuzda yaptığınız tek şey uzun süren yöntemi ana iş parçacığına geri aktarmaktır. Tabii ki, bu gerçekten yapmak istediğiniz şey değil. Ana iş parçacığında yaptığınız her şey burada ve orada hızlı bir özellik ayarlamak için bunu biraz yeniden düşünmeniz gerekiyor.



10

Async / Await ve geri çağrıları kullanarak yeni bir görünüm. Uzantı yöntemini projenizde tutarsanız yalnızca bir kod satırına ihtiyacınız vardır.

/// <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;
    }
}

Uzantı yöntemine, Try / Catch deyiminde sarmalamak ve arayanın tamamlandıktan sonra hangi türün geri döneceğini söylemesine izin vermek gibi başka şeyler ekleyebilirsiniz.

Yakalamayı Dene, Otomatik İstisna Günlüğü ve Geri Arama Ekleme

    /// <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
        }
    }

10

Bu hatayı çözmek için önerilen yol değildir, ancak hızlı bir şekilde bastırabilirsiniz, işi yapacak. Bunu prototipler veya demolar için tercih ederim. Ekle

CheckForIllegalCrossThreadCalls = false

içinde Form1()yapıcısı.


9

Başka bir iş parçacığından nesneleri değiştirmek için en basit (bence) yolunu izleyin:

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;
        }
    }
}

Çok basit! Teşekkürler .
Ali Esmaeili


7

Xamarin stuidio dışındaki bir görsel stüdyoda winforms prototip projesinde bir iOS-Phone monotouch uygulama denetleyicisi programlarken buna ihtiyaç duydum. VS'de xamarin stüdyosu üzerinden mümkün olduğunca programlamayı tercih ederek, kontrol cihazının telefon çerçevesinden tamamen ayrılmasını istedim. Bu şekilde, Android ve Windows Phone gibi diğer çerçeveler için bunu uygulamak, gelecekteki kullanımlar için çok daha kolay olacaktır.

GUI'nin her düğmeye tıklamanın ardındaki çapraz diş açma anahtar koduyla uğraşma yükü olmadan olaylara yanıt verebileceği bir çözüm istedim. Temel olarak, sınıf denetleyicisinin istemci kodunu basit tutmak için işlemesine izin verin. Muhtemelen GUI üzerinde birçok olay olabilir, burada sanki sınıfın bir yerinde halledebilirdiniz. Ben çok önde gelen bir uzman değilim, bunun kusurlu olup olmadığını bana bildirin.

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");
    }
}

GUI formu, denetleyicinin eşzamansız görevler çalıştırdığından habersizdir.

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);
            }
        }
    }
}

6

Çalıştığınız nesnenin sahip olmadığı alternatif bir yol

(InvokeRequired)

Ana formda, ana formdan başka bir sınıfta, ana formda olan, ancak InvokeRequired özelliği bulunmayan bir nesneyle çalışıyorsanız bu yararlıdır.

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;
}

Yukarıdakiyle aynı şekilde çalışır, ancak invokerequired olan bir nesneniz yoksa, ancak MainForm'a erişiminiz varsa farklı bir yaklaşımdır.


5

Önceki yanıtlarla aynı satırlar boyunca, ancak çapraz kontrol çağırma istisnası olmadan tüm Kontrol özelliklerini kullanmanıza izin veren çok kısa bir ekleme.

Yardımcı Yöntem

/// <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;
}

Örnek Kullanımı

// 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;
}


5

Örneğin, metni UI iş parçacığının bir Denetiminden almak için:

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

3

Aynı soru: başka bir iş parçacığından-gui-nasıl-güncellenir

İki yol:

  1. E.result içinde değer döndürün ve backgroundWorker_RunWorkerCompleted etkinliğinde metin kutusu değerini ayarlamak için kullanın

  2. Bu tür değerleri ayrı bir sınıfta tutacak bazı değişkenler bildirin (veri sahibi olarak çalışacaktır). Bu sınıfın statik örneğini oluşturun ve herhangi bir evre üzerinden erişebilirsiniz.

Misal:

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";
    }
}

2

Sadece şunu kullanın:

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

0

Eylem y; // sınıf içinde ilan edildi

label1.Invoke (y = () => Label1.Text = "metin");


0

Bu soruna geçici bir çözüm bulmak için basit ve yeniden kullanılabilir bir yol.

Genişletme Yöntemi

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

Örnek Kullanımı

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

-3

Çapraz iplik işlemleri için iki seçenek vardır.

Control.InvokeRequired Property 

ve ikincisi kullanmak

SynchronizationContext Post Method

Control.InvokeRequired yalnızca SynchronizationContext herhangi bir yerde kullanılabilirken Control sınıfından devralınan çalışma kontrolleri için kullanışlıdır. Bazı yararlı bilgiler aşağıdaki bağlantılardır

Çapraz Konu Güncelleme Kullanıcı Arayüzü | .Ağ

SynchronizationContext kullanarak Çapraz Güncelleme UI | .Ağ

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.