InvokeRequired kod modelini otomatikleştirme


179

Acı ne sıklıkta olay olay güdümlü GUI kodunda aşağıdaki kod desenini yazmak için ne kadar sık ​​gerektiğini fark var

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

dönüşür:

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

Bu, hem hatırlamak hem de yazmak için C # 'daki garip bir modeldir. Herkes bunu bir dereceye kadar otomatikleştiren bir tür kısayol veya yapı ile geldi mi? Bu kontrolü yapan nesnelere, bir object1.InvokeIfNecessary.visible = truetür kısayol gibi, tüm bu fazladan işi yapmak zorunda kalmadan bir işlev eklemenin bir yolu olsaydı havalı olurdu .

Önceki cevaplar sadece Invoke () her zaman aradığını pratik olmadığının ele aldık ve o zaman bile Çağırma () sözdizimi verimsiz ve hem hala başa garip.

Peki, herhangi bir kısayol çözen var mı?


2
Aynı şeyi merak ettim, ama WPF'nin Dispatcher.CheckAccess () ile ilgili olarak.
Taylor Leese

Hattınızdan ilham alan oldukça çılgın bir öneri düşündüm object1.InvokeIfNecessary.Visible = true; güncellenmiş cevabımı kontrol et ve bana ne düşündüğünü söyle.
Dan Tao

1
Matt Davis tarafından önerilen yöntemin uygulanmasına yardımcı olmak için bir Snippet ekleyin: cevabımı görün (geç ama daha sonra okuyucular için nasıl olduğunu gösteriyor ;-))
Aaron Gage

3
Microsoft'un .NET'te bunu basitleştirmek için neden hiçbir şey yapmadığını anlamıyorum. İş parçacığından formdaki her değişiklik için delege oluşturmak gerçekten sinir bozucu.
Kamil

@Kamil Daha fazla anlaşamadım! Yaygınlığı göz önüne alındığında, bu böyle bir gözetim. Çerçeve dahilinde, gerekirse iplik geçirme işlemini gerçekleştirin. Açık görünüyor.
SteveCinq

Yanıtlar:


138

Lee'nin yaklaşımı daha da basitleştirilebilir

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

Ve bu şekilde çağrılabilir

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

Denetimi parametre olarak delege olarak iletmeye gerek yoktur. C # otomatik olarak bir kapak oluşturur .


GÜNCELLEME :

Diğer birkaç afişe göre Controlgenelleştirilebilir 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 Control, ISynchronizeInvokearabirimin aksine Invokeyöntem için parametre listesi olarak yöntem için bir nesne dizisi gerektirdiğine dikkat çekti action.


GÜNCELLEME 2

Mike de Klerk tarafından önerilen düzenlemeler (ekleme noktası için 1. kod snippet'indeki yoruma bakın):

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

Bu öneriyle ilgili endişeler için aşağıdaki ToolmakerSteve'in yorumuna bakın.


2
Olması iyi olmaz ISynchronizeInvokeyerine Control? (Kudos - Jon Skeet stackoverflow.com/questions/711408/… )
Odys

@odyodyodys: İyi nokta. Bilmiyordum ISynchronizeInvoke. Ancak ondan türeyen tek tip (Reflektöre göre) Control, bu yüzden adavantage sınırlıdır.
Olivier Jacot-Descombes

3
@ mike-de-clerk, eklemek için öneri konusunda endişeliyim while (!control.Visible) ..sleep... Bana göre, kötü bir kod kokusu vardır, çünkü potansiyel olarak sınırlanmamış bir gecikme (belki de bazı durumlarda sonsuz bir döngü), kodda böyle bir gecikme (veya bir kilitlenme) beklemeyen arayanlar olabilir. IMHO, herhangi bir kullanımı Sleepher arayanın sorumluluğunda olmalıdır VEYA sonuçlarıyla açıkça işaretlenmiş ayrı bir ambalajda olmalıdır. IMHO, genellikle "başarısız olmak" (istisna, test sırasında yakalamak) veya kontrol hazır değilse "hiçbir şey yapmamak" daha iyi olur. Yorumlar?
ToolmakerSteve

1
@ OlivierJacot-Descombes, thread.invokerequired'in nasıl çalıştığını açıklarsanız harika olur mu?
Sudhir.net

1
InvokeRequiredçağıran evrenin kontrolü oluşturan evreden farklı olup olmadığını belirtir. Invokeeylemi çağıran evreden denetimin yürütüldüğü iş parçacığına geçirir. Bu, örneğin bir tıklama olayı işleyicisinin hiçbir zaman kesintiye uğramamasını sağlar.
Olivier Jacot-Descombes

133

Bir uzantı yöntemi yazabilirsiniz:

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

Ve şu şekilde kullanın:

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

DÜZENLEME: Simpzon'un yorumlarda belirttiği gibi imzayı şu şekilde de değiştirebilirsiniz:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

Belki çok aptalım ama bu kod derlenmeyecek. Bu yüzden benim tarafımdan inşa edildiği gibi (VS2008) sabitledim.
Oliver

5
Sadece bütünlük için: WPF'de farklı bir sevk mekanizması vardır, ancak oldukça benzer şekilde çalışır. Orada bu uzantı yöntemini kullanabilirsiniz: public static void InvokeIfRequired <T> (bu T aTarget, Action <T> aActionToExecute) burada T: DispatcherObject {if (aTarget.CheckAccess ()) {aActionToExecute (aTarget); } else {aTarget.Dispatcher.Invoke (aActionToExecute); }}
Simon D.

1
Lee'nin çözümünü biraz basitleştiren bir cevap ekledim.
Olivier Jacot-Descombes

Merhaba, benzer bir şey kullandığım için, bu genel uygulamadan gelen büyük bir sorun olabilir. Denetim elden çıkarıyor / elden çıkarılıyorsa, bir ObjectDisposedException alıyorsunuz.
Offler

1
@Offler - Farklı bir iş parçacığına atılıyorlarsa, bir senkronizasyon sorununuz varsa, bu yöntemde bir sorun yoktur.
Lee

33

İşte tüm kodumda kullandığım form.

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

Bunu buradaki blog girişine dayandım . Bu yaklaşım beni başarısız oldu, bu yüzden InvokeRequiredözelliği kontrol ile kodumu karmaşıklaştırmak için hiçbir neden görmedim .

Bu yardımcı olur umarım.


+1 - Yaptığınız aynı blog girişinde tökezledim ve bunun önerilenlerin en temiz yaklaşımı olduğunu düşünüyorum
Tom Bushell

3
Bu yaklaşımı kullanarak, birden fazla kez çağrıldığında birikebilen küçük bir performans isabeti vardır. stackoverflow.com/a/747218/724944
surfen

4
Sen kullanmak zorunda InvokeRequiredkod infaz edilip edilemeyeceğini kontrol gösterildi önce veya ölümcül istisna olacaktır.
56ka

9

Bir ThreadSafeInvoke.snippet dosyası oluşturun ve ardından güncelleme bildirimlerini seçebilir, sağ tıklayıp 'Surround ...' veya Ctrl-K + S'yi seçebilirsiniz:

<?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>

6

İşte Lee'nin, Oliver'ın ve Stephan'ın cevaplarının geliştirilmiş / birleştirilmiş versiyonu.

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

Şablon, atanmış delege verimlilik sağlarken çok daha okunabilir esnek ve dökümsüz kod sağlar.

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

4

Her seferinde yeni bir örnek oluşturmak yerine tek bir yöntem Temsilcisi kullanmak istiyorum. Benim durumumda bir Backroundworker kopyalama ve sql örneğinden büyük veri döküm mesajları (bilgi / hata) göstermek için kullanılır. Yaklaşık 70000 ilerleme ve mesaj aramasından sonra formum çalışmayı durdurdu ve yeni mesajlar gösterdi. Tek bir genel yönetim ortamı temsilcisi kullanmaya başladığımda bu gerçekleşmedi.

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

3

Kullanımı:

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

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

    return c.Visible;
});

Kod:

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

2

Biraz farklı yapmak istiyorum, bir Eylem ile gerekirse "kendim" demeyi seviyorum,

    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

Bu kullanışlı bir desen, IsFormClosing hala çalışan bazı arka plan iş parçacıkları olabilir gibi formumu kapatırken True olarak ayarladığım bir alandır ...


-3

Asla şöyle görünen bir kod yazmamalısınız:

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

Buna benzeyen bir kodunuz varsa, uygulamanız iş parçacığı için güvenli değildir. Farklı bir iş parçacığından zaten DoGUISwitch () öğesini çağıran kodunuz olduğu anlamına gelir. Farklı bir iş parçacığında olup olmadığını kontrol etmek için çok geç. DogUISwitch'i aramadan ÖNCE InvokeRequire çağrılmalıdır. Farklı bir iş parçacığından herhangi bir yönteme veya özelliğe erişmemelisiniz.

Başvuru: Aşağıdakileri okuyabileceğiniz Control.InvokeRequired Özelliği :

InvokeRequired özelliğine ek olarak, bir denetimde işlenecek iş parçacığı için güvenli olan dört yöntem vardır: Denetimin tanıtıcısı zaten oluşturulmuşsa, Invoke, BeginInvoke, EndInvoke ve CreateGraphics.

Tek bir CPU mimarisinde sorun yoktur, ancak çoklu CPU mimarisinde UI iş parçacığının bir kısmının arama kodunun çalıştığı işlemciye atanmasına neden olabilirsiniz ve bu işlemci UI iş parçacığından farklıysa o zaman çalışan iş parçacığı bittiğinde Windows UI iş parçacığının sona erdiğini düşünecek ve uygulama işlemini öldürecek yani uygulamanız hatasız çıkacaktır.


Hey, cevabın için teşekkürler. Bu soruyu sordumdan bu yana yıllar geçti (ve neredeyse C # ile çalıştığımdan beri uzun bir süre), ama biraz daha açıklayabilir misiniz diye merak ediyordum? Bağlandığınız dokümanlar invoke(), kontrole bir tanıtıcı verilmeden önce belirli bir arama tehlikesine atıfta bulunur , ancak IMHO açıkladığınız şeyi tanımlamaz. Tüm bu invoke()saçmalık, UI iş parçacığı güvenli bir şekilde güncellemektir ve engelleme bağlamında daha fazla talimat koymak kekemelik yol açacağını düşünürdüm? (Ugh ... M $ teknolojisini kullanmayı bıraktığım için mutluyum. Çok karmaşık!)
Tom Corelis

Ayrıca, orijinal kodun sık kullanılmasına rağmen (geri dönüş yolu), çift işlemcili masaüstümde tanımladığınız sorunu gözlemlemediğimi de
belirtmek isterim

3
MSDN, OP'nin verdiği gibi birçok örnek gösterdiğinden bu cevabın doğru olduğundan şüpheliyim.
halka açık kablosuz
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.