2009-12-22 12 views
11
için

için UI olayları göndermek için SenkronizasyonKontext'i kullanma Bir çok çok iş parçacıklı arka plan görevleri yapan DLL'mden UI iş parçacığına geri dönen bir SynchronizationContext kullanıyorum.WinForms veya WPF

Tekil kalıpların favori olmadığını biliyorum, ancak foo'nun üst nesnesini oluştururken UI'nin SynchronizationContext öğesinin bir başvurusunu saklamak için şimdi kullanıyorum.

public class Foo 
{ 
    public event EventHandler FooDoDoneEvent; 

    public void DoFoo() 
    { 
     //stuff 
     OnFooDoDone(); 
    } 

    private void OnFooDoDone() 
    { 
     if (FooDoDoneEvent != null) 
     { 
      if (TheUISync.Instance.UISync != SynchronizationContext.Current) 
      { 
       TheUISync.Instance.UISync.Post(delegate { OnFooDoDone(); }, null); 
      } 
      else 
      { 
       FooDoDoneEvent(this, new EventArgs()); 
      } 
     } 

    } 
} 

Bu hiç WPF işe yaramadı (ana pencereden yem edilir) TheUISync örneklerini UI senkron asla geçerli SynchronizationContext.Current eşleşir. Windows formunda ben de aynı şeyi yaptığımda, bir invoke edildikten sonra eşleşecekler ve biz doğru dişe geri döneceğiz.

public class Foo 
{ 
    public event EventHandler FooDoDoneEvent; 

    public void DoFoo() 
    { 
     //stuff 
     OnFooDoDone(false); 
    } 

    private void OnFooDoDone(bool invoked) 
    { 
     if (FooDoDoneEvent != null) 
     { 
      if ((TheUISync.Instance.UISync != SynchronizationContext.Current) && (!invoked)) 
      { 
       TheUISync.Instance.UISync.Post(delegate { OnFooDoDone(true); }, null); 
      } 
      else 
      { 
       FooDoDoneEvent(this, new EventArgs()); 
      } 
     } 

    } 
} 

Yani bu örnek izleyecek kadar mantıklı umut gibi

Benim düzeltme, nefret, hangi görünüyor.

cevap

37

acil sorun

Kişisel acil sorun SynchronizationContext.Current otomatik WPF için ayarlanmamış olmasıdır. WPF altında çalışırken bunu ayarlayabilmek için TheUISync kodunda böyle bir şey yapmak gerekecektir:

var context = new DispatcherSynchronizationContext(
        Application.Current.Dispatcher); 
SynchronizationContext.SetSynchronizationContext(context); 
UISync = context; 

SynchronizationContext COM + desteği ile bağlıdır ve konuları geçmeye tasarlanmış bir derin sorunu . WPF'de birden fazla iş parçacığı içeren bir Dispatcher'ınız olamaz, dolayısıyla bir SynchronizationContext gerçekten iş parçacıklarını geçemez. SynchronizationContext'un yeni bir konuya geçebileceği bir dizi senaryo vardır - özellikle ExecutionContext.Run() numaralı telefonu arayarak. Bu nedenle, hem WinForms hem de WPF istemcilerine etkinlik sağlamak için SynchronizationContext kullanıyorsanız, bazı senaryoların kesileceğinin farkında olmanız gerekir; örneğin, bir web hizmetine veya aynı işlemde barındırılan siteye yönelik bir web isteği sorun olur.

bile nasıl WinForms koduyla, bu amaç için özel olarak WPF'ın Dispatcher mekanizmasını kullanarak önermek Bu nedenle

SynchronizationContext gerek aşmanın. Senkronizasyonu saklayan bir "TheISISync" singleton sınıfı oluşturdunuz, böylece uygulamanın en üst seviyesine girmenin bir yolu var. Ancak, bunu yaptığınızda, kodun, WFF içeriğinize bazı WPF içeriği ekleyebilmeniz için Dispatcher'un işe yarayacağını, sonra da aşağıda açıkladığım yeni Dispatcher mekanizmasını kullanabileceğinizi de ekleyebilirsiniz.

Dispatcher yerine SynchronizationContext kullanma

WPF'ın Dispatcher mekanizma aslında ayrı SynchronizationContext nesne için gereksinimini ortadan kaldırır.COM + nesneleri veya WinForms UI'leri gibi paylaşım kodları gibi bazı birlikte çalışma senaryolarınız olmadıkça, en iyi çözüm, SynchronizationContext yerine Dispatcher'u kullanmaktır.

Bu gibi görünüyor: artık bir TheUISync nesneye ihtiyacınız

public class Foo 
{ 
    public event EventHandler FooDoDoneEvent; 

    public void DoFoo() 
    { 
    //stuff 
    OnFooDoDone(); 
    } 

    private void OnFooDoDone() 
    { 
    if(FooDoDoneEvent!=null) 
     Application.Current.Dispatcher.BeginInvoke(
     DispatcherPriority.Normal, new Action(() => 
     { 
      FooDoDoneEvent(this, new EventArgs()); 
     })); 
    } 
} 

Not - WPF sizin için o ayrıntıyı ele alır.

Eski delegate sözdizimi ile daha rahat iseniz bunu yerine bu şekilde yapabilirsiniz: Bir ilişkisiz hata Ayrıca bir hata var olduğuna dikkat

düzeltmek için

 Application.Current.Dispatcher.BeginInvoke(
     DispatcherPriority.Normal, new Action(delegate 
     { 
      FooDoDoneEvent(this, new EventArgs()); 
     })); 

senin burada çoğaltılan orijinal kod. Sorun, FooDoneEvent öğesinin, OnFooDoDone öğesinin çağrıldığı ve BeginInvoke (veya orijinal koddaki Post) süresinin temsilci çağırdığı zaman arasında null olarak ayarlanabilmesidir. Düzeltme, delegenin içinde ikinci bir testtir:

if(FooDoDoneEvent!=null) 
     Application.Current.Dispatcher.BeginInvoke(
     DispatcherPriority.Normal, new Action(() => 
     { 
      if(FooDoDoneEvent!=null) 
      FooDoDoneEvent(this, new EventArgs()); 
     })); 
+0

Bir şeyi gerçekten kilitlemediğiniz sürece, ne zaman kontrol ettiğinize bakılmaksızın, her zaman bu problemi yaşayabileceğinizi düşünüyorum. Ve bu, sınıfınızda sabitlenemeyen bir iş parçacığı sorunudur, olayınıza kablolama yapan sınıfta ele alınmalıdır (ya da etkinliğinizden çıkarılamıyor), ancak bir kilitleme nesnesi sağlamak zorundayım. Abone kullanabilir ve olayların senkronize olduğundan emin olmak için kullanabilirdim. –

+1

Evet ve hayır. "kamu olayı ...", serbest iş parçacığı olarak adlandırılabileceğini varsayarsanız, kendi başına sorunları işliyor. İki iş parçacığının her ikisi de aynı anda aynı etkinliğe abone olursa, ikisi de çağrı listesine giremeyebilir. Bu yüzden varsayım, her zaman olayı ilk etapta ayarlamak için akıl yürütme protokolüne sahip olmanızdır. Bu tür en yaygın protokol, yalnızca UI iş parçacığı üzerinde bu tür olayları ayarlamaktır; bu durumda, hata düzeltmesi, güvenilir bir şekilde çalışır, oysa orijinal kod yoktur. Diğer taraftan, farklı bir protokol amaçlanmışsa, açık kilitlemeye ihtiyacınız olabilir. –

+2

FooDoDoneEvent öğesinin boş olmadığından emin olmak için, gerçekten yapmak istediğiniz şeyin yerel bir değişkene FooDoDoneEvent öğesini ataması, boş olmadığını kontrol etmesi ve sonra FooDoDoneEvent (bu, yeni EventArgs()) çağırması gerektiğine inanıyorum. '. – Charles

0

Geçerli olanla karşılaştırmak yerine, neden sadece 'u dert etmeyin; o zaman "hayır bağlam" talebi işleme bir olgu basitçe:

static void RaiseOnUIThread(EventHandler handler, object sender) { 
    if (handler != null) { 
     SynchronizationContext ctx = SynchronizationContext.Current; 
     if (ctx == null) { 
      handler(sender, EventArgs.Empty); 
     } else { 
      ctx.Post(delegate { handler(sender, EventArgs.Empty); }, null); 
     } 
    } 
} 
+0

Bunun neden başarısız olduğunu anlıyorum, ancak bunu zaten denediğimi biliyorum. Winform test uygulaması, birim testleri veya WPF uygulaması olup olmadığını hatırlayamıyorum, ancak bu çözüm, bu teknolojilerin en az biriyle uyumsuzdu. –

+3

Bu kod SynchronizationContext.Current olarak çalışmayacak. UI iş parçacığı üzerinde "yakalanmalı", RaiseOnUIThread başka bir iş parçacığı üzerinde çağrılabilir. –