SynchronizationContext ne yapar?


135

Programlama C # kitabında aşağıdakilerle ilgili bazı örnek kodlar vardır SynchronizationContext:

SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
    string text = File.ReadAllText(@"c:\temp\log.txt");
    originalContext.Post(delegate {
        myTextBox.Text = text;
    }, null);
});

Konularda yeni başlayan biriyim, bu yüzden lütfen ayrıntılı olarak cevaplayın. Birincisi, bağlamın ne anlama geldiğini bilmiyorum, program originalContext? Ve Postyöntem çalıştırıldığında, UI iş parçacığı ne yapacak?
Aptalca şeyler sorarsam lütfen beni düzeltin, teşekkürler!

DÜZENLEME: Örneğin, sadece myTextBox.Text = text;yöntemi yazarsam, ne fark eder?


1
İnce el kitabında şunu söyleyebiliriz : Bu sınıf tarafından uygulanan senkronizasyon modelinin amacı, ortak dil çalışma zamanının dahili asenkron / senkronizasyon işlemlerinin farklı senkronizasyon modelleriyle düzgün şekilde davranmasına izin vermektir. Bu model aynı zamanda, yönetilen uygulamaların farklı senkronizasyon ortamlarında doğru şekilde çalışması için uyması gereken bazı gereksinimleri basitleştirir.
ta.speot.is

IMHO async zaten bunu yapıyor
Royi Namir

7
@RoyiNamir: Evet, ama tahmin et ne: async/ altına awaitgüveniyor SynchronizationContext.
stakx - artık

Yanıtlar:


170

SynchronizationContext ne yapar?

Basitçe söylemek gerekirse, SynchronizationContextkodun "nerede" yürütülebileceği bir konumu temsil eder. Onun Sendveya Postyöntemine aktarılan temsilciler daha sonra bu konumda çağrılacaktır. ( Postöğesinin engellemeyen / eşzamansız sürümüdür Send.)

Her iş parçacığının SynchronizationContextkendisiyle ilişkilendirilmiş bir örneği olabilir . Çalışan evre, statik SynchronizationContext.SetSynchronizationContextyöntem çağrılarak bir senkronizasyon bağlamı ile ilişkilendirilebilir ve çalışan iş parçacığının geçerli bağlamı, SynchronizationContext.Currentözellik aracılığıyla sorgulanabilir .

Az önce yazdıklarıma rağmen (her iş parçacığı ilişkili bir eşzamanlama bağlamına sahip), a SynchronizationContextmutlaka belirli bir iş parçacığını temsil etmiyor ; ayrıca kendisine aktarılan delegelerin çağrısını birkaçThreadPool iş parçacığından herhangi birine (örneğin bir çalışan iş parçacığına) veya (en azından teoride) belirli bir CPU çekirdeğine veya hatta başka bir ağ ana bilgisayarına iletebilir . Temsilcilerinizin nerede çalışmaya başlayacağı, SynchronizationContextkullanılan türüne bağlıdır .

Windows Forms WindowsFormsSynchronizationContext, ilk formun oluşturulduğu iş parçacığına bir yükler . (Bu iş parçacığı genellikle "UI iş parçacığı" olarak adlandırılır.) Bu tür bir eşitleme bağlamı, tam olarak bu iş parçacığı üzerinde kendisine iletilen delegeleri çağırır. Windows Forms, diğer birçok UI çerçevesi gibi, yalnızca oluşturuldukları aynı iş parçacığı üzerindeki denetimlerin değiştirilmesine izin verdiği için bu çok kullanışlıdır.

Ya sadece myTextBox.Text = text;yöntemi yazarsam, ne fark eder?

İlettiğiniz kod ThreadPool.QueueUserWorkItem, bir iş parçacığı havuzu çalışan iş parçacığında çalıştırılacaktır. Yani, sizin myTextBoxoluşturduğunuz iş parçacığı üzerinde çalıştırılmayacaktır , bu nedenle Windows Forms er ya da geç (özellikle Sürüm sürümlerinde) bir istisna atarak myTextBoxbaşka bir iş parçacığından erişemeyeceğinizi söyler .

Bu nedenle myTextBox, o belirli atamadan önce bir şekilde çalışan iş parçacığından "UI iş parçacığı" na ( yaratıldığı yer) "geri dönmeniz" gerekir . Bu şu şekilde yapılır:

  1. Hala UI iş parçacığındayken, SynchronizationContextorada Windows Forms'u yakalayın ve originalContextdaha sonra kullanmak için bir değişkene ( ) bir referans kaydedin. SynchronizationContext.CurrentBu noktada sorgulamalısınız ; eğer onu iletilen kodun içinde sorguladıysanız ThreadPool.QueueUserWorkItem, iş parçacığı havuzunun çalışan iş parçacığı ile ilişkilendirilmiş olan her türlü eşitleme bağlamını elde edebilirsiniz. Windows Forms bağlamına bir başvuru kaydettikten sonra, kodu UI iş parçacığına "göndermek" için her yerde ve her zaman kullanabilirsiniz.

  2. Bir UI öğesini değiştirmeniz gerektiğinde (ancak artık UI iş parçacığında değil veya olmayabilir), aracılığıyla Windows Forms'un senkronizasyon bağlamına erişin originalContextve UI'yi ya Sendda Post.


Son açıklamalar ve ipuçları:

  • Ne senkronizasyon bağlamları olmaz sizin için ne hangi kod belirli bir konum / bağlamında çalıştırmalısınız anlatıyor ve hangi kod sadece geçirmeden olmadan normal olarak çalıştırılabilir SynchronizationContext. Buna karar vermek için, programladığınız çerçevenin kurallarını ve gereksinimlerini bilmeniz gerekir - bu durumda Windows Forms.

    Bu nedenle, Windows Forms için şu basit kuralı unutmayın: Denetimlere veya formlara, onları oluşturan iş parçacığından başka bir iş parçacığından ERİŞMEYİN. Bunu yapmanız gerekiyorsa, SynchronizationContextyukarıda açıklanan mekanizmayı kullanın veya Control.BeginInvoke(bu, aynı şeyi yapmanın Windows Forms'a özgü bir yoludur).

  • Olduğunuz programlama .NET 4.5 karşı ya da geç, o açıkça kodunuzu dönüştürerek hayat çok daha kolay yapabilir kullanımları SynchronizationContext, ThreadPool.QueueUserWorkItem, control.BeginInvokevb üzerinde yeniye async/ awaitanahtar kelimeler ve Görev Paralel Kütüphanesi (VUK) yani çevreleyen API, Taskve Task<TResult>sınıflar. Bunlar, çok yüksek bir dereceye kadar, UI iş parçacığının eşitleme bağlamını yakalamaya, eşzamansız bir işlem başlatmaya ve ardından işlemin sonucunu işleyebilmeniz için UI iş parçacığına geri dönmeye özen gösterir.


Söyleyecek Windows Forms, diğer birçok UI çerçeveler gibi, yalnızca aynı iş parçacığı üzerinde kontrollerin manipülasyon izin ancak Windows tüm pencereleri oluşturulduğu aynı parçacığı tarafından erişilebilir olmalıdır.
user34660

4
@ user34660: Hayır, bu doğru değil. Sen olabilir Windows Forms denetimleri oluşturmak birkaç konuları var. Ancak her kontrol, onu oluşturan tek bir iş parçacığı ile ilişkilidir ve yalnızca o iş parçacığı tarafından erişilmesi gerekir. Farklı UI iş parçacıklarından gelen kontroller, birbirleriyle nasıl etkileşimde bulundukları konusunda da çok sınırlıdır: biri diğerinin ebeveyni / çocuğu olamaz, bunlar arasında veri bağlama mümkün değildir, vb. Son olarak, kontrolleri oluşturan her iş parçacığının kendi mesajına ihtiyacı vardır. döngü ( Application.RunIIRC ile başlar). Bu oldukça gelişmiş bir konudur ve gelişigüzel yapılan bir şey değildir.
stakx -

İlk yorumum, "diğer birçok UI çerçevesi gibi" demenizdir, bu da bazı pencerelerin farklı bir iş parçacığından "kontrollerin değiştirilmesine" izin verdiğini , ancak hiçbir Windows penceresinin bunu yapmadığını ima eder . Aynı pencere için "Windows Forms denetimleri oluşturan birkaç iş parçacığına sahip olamazsınız" ve "aynı iş parçacığı tarafından erişilmelidir" ve "yalnızca bu iş parçacığı tarafından erişilmelidir" aynı şeyi söyler. Aynı pencere için "farklı UI iş parçacıklarından kontroller" oluşturmanın mümkün olduğundan şüpheliyim. Tüm bunlar, .Net'ten önce Windows programlama konusunda deneyimli bizler için ileri düzeyde değildir.
user34660

3
"Pencereler" ve "Windows pencereleri" hakkındaki tüm bu konuşma başımı döndürüyor. Bu "pencerelerden" herhangi birinden bahsetmiş miydim? Sanmıyorum ...
stakx -

1
@ibubi: Sorunuzu anladığımdan emin değilim. Herhangi bir iş parçacığının senkronizasyon bağlamı ya set ( null) ya da bir örneği SynchronizationContext(ya da onun bir alt sınıfı) değildir. Bu teklifin amacı, elde ettiğiniz değil, elde edemeyeceğiniz şeydi : UI iş parçacığının senkronizasyon bağlamı.
stakx -

24

Diğer cevaplara eklemek istiyorum, SynchronizationContext.Postsadece hedef iş parçacığında (normalde hedef iş parçacığının mesaj döngüsünün bir sonraki döngüsü sırasında) daha sonra yürütmek için bir geri aramayı sıraya koyar ve ardından yürütme çağrı iş parçacığında devam eder. Öte yandan, SynchronizationContext.Sendgeri aramayı hedef iş parçacığı üzerinde hemen yürütmeye çalışır, bu da çağrı iş parçacığını engeller ve kilitlenmeye neden olabilir. Her iki durumda da, kod yeniden giriş olasılığı vardır (aynı yönteme yapılan önceki çağrı dönmeden önce aynı yürütme iş parçacığına bir sınıf yöntemi girme).

Win32 programlama modeline aşina iseniz , hedef pencereninkinden farklı bir iş parçacığından bir ileti göndermek için arayabileceğiniz çok yakın bir benzetme PostMessageve SendMessageAPI'ler olacaktır .

İşte senkronizasyon bağlamlarının ne olduğuna dair çok iyi bir açıklama: Her Şey SynchronizationContext Hakkında .


16

SynchronizationContext'ten türetilen bir sınıf olan senkronizasyon sağlayıcısını depolar. Bu durumda, bu büyük olasılıkla bir WindowsFormsSynchronizationContext örneği olacaktır. Bu sınıf, Send () ve Post () yöntemlerini uygulamak için Control.Invoke () ve Control.BeginInvoke () yöntemlerini kullanır. Veya DispatcherSynchronizationContext olabilir, Dispatcher.Invoke () ve BeginInvoke () kullanır. Bir Winforms veya WPF uygulamasında, bir pencere oluşturduğunuzda bu sağlayıcı otomatik olarak yüklenir.

Kod parçacığında kullanılan iş parçacığı havuzu iş parçacığı gibi başka bir iş parçacığı üzerinde kod çalıştırdığınızda, iş parçacığı açısından güvenli olmayan nesneleri doğrudan kullanmamaya dikkat etmelisiniz. Herhangi bir kullanıcı arabirimi nesnesi gibi, TextBox'ı oluşturan zincirden TextBox.Text özelliğini güncellemeniz gerekir. Post () yöntemi, temsilci hedefin bu iş parçacığı üzerinde çalışmasını sağlar.

Bu kod parçacığının biraz tehlikeli olduğuna dikkat edin, yalnızca UI iş parçacığından çağırdığınızda doğru şekilde çalışacaktır. SynchronizationContext.Current farklı evrelerde farklı değerlere sahiptir. Yalnızca UI iş parçacığı kullanılabilir bir değere sahiptir. Ve kodun onu kopyalamasının nedeni budur. Bir Winforms uygulamasında bunu yapmanın daha okunaklı ve daha güvenli bir yolu:

    ThreadPool.QueueUserWorkItem(delegate {
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.BeginInvoke(new Action(() => {
            myTextBox.Text = text;
        }));
    });

Herhangi bir iş parçacığından çağrıldığında çalışması avantajına sahiptir . SynchronizationContext.Current kullanmanın avantajı, kodun Winforms veya WPF'de kullanılıp kullanılmadığına bakılmaksızın yine de çalışmasıdır, bir kitaplıkta önemlidir. Bu kesinlikle böyle bir kod için iyi bir örnek değildir , burada ne tür bir TextBox'a sahip olduğunuzu her zaman bilirsiniz, böylece Control.BeginInvoke veya Dispatcher.BeginInvoke kullanıp kullanmayacağınızı her zaman bilirsiniz. Aslında SynchronizationContext.Current kullanımı o kadar yaygın değildir.

Kitap size iplik geçirmeyi öğretmeye çalışıyor, bu yüzden bu kusurlu örneği kullanmak sorun değil. Gerçek hayatta, sen birkaç durumda olabilir SynchronizationContext.Current kullanmayı düşünün, hala C # için o kadar terk ediyorum 'ın zaman uyumsuz / bekliyoruz anahtar kelimeler veya TaskScheduler.FromCurrentSynchronizationContext () sizin için bunu yapmak için. Ancak, aynı nedenden ötürü, onları yanlış iş parçacığı üzerinde kullandığınızda pasajın yaptığı gibi hala yanlış davrandıklarını unutmayın. Buralarda çok yaygın bir soru, ekstra soyutlama seviyesi kullanışlıdır, ancak neden doğru çalışmadıklarını anlamayı zorlaştırır. Umarım kitap size onu ne zaman kullanmamanız gerektiğini de söyler :)


Üzgünüm, neden UI iş parçacığı tutamacının iş parçacığı açısından güvenli olmasına izin verilsin? yani, Post () ateşlendiğinde UI iş parçacığının myTextBox kullanıyor olabileceğini düşünüyorum, bu güvenli mi?
cloudyFan

4
İngilizcenizin kodunu çözmek zor. Orijinal snippet'iniz yalnızca UI iş parçacığından çağrıldığında doğru çalışır. Bu çok yaygın bir durumdur. Ancak o zaman UI başlığına geri gönderilecektir. Bir işçi iş parçacığından çağrılırsa, Post () temsilci hedefi bir iş parçacığı iş parçacığı üzerinde çalışacaktır. Kaboom. Bu, kendiniz için denemek isteyeceğiniz bir şey. Bir iş parçacığı başlatın ve iş parçacığının bu kodu çağırmasına izin verin. Kod bir NullReferenceException ile çökerse doğru yaptınız.
Hans Passant

5

Buradaki senkronizasyon bağlamının amacı myTextbox.Text = text;, ana UI iş parçacığında çağrıldığından emin olmaktır .

Windows, GUI denetimlerine yalnızca oluşturuldukları iş parçacığı tarafından erişilmesini gerektirir. Metni önce senkronize etmeden bir arka plan iş parçacığına atamayı denerseniz (bu veya Invoke kalıbı gibi birkaç yolla), o zaman bir istisna atılır.

Bunun yaptığı şey, arka plan iş parçacığını oluşturmadan önce eşitleme bağlamını kaydetmektir, ardından arka plan iş parçacığı bağlamı kullanır. Post yöntemi GUI kodunu yürütür.

Evet, gösterdiğiniz kod temelde işe yaramaz. Neden yalnızca ana UI iş parçacığına hemen geri dönmek için arka plan dizisi oluşturalım? Bu sadece bir örnek.


4
"Evet, gösterdiğiniz kod temelde işe yaramaz. Neden bir arka plan iş parçacığı oluşturuyorsunuz, yalnızca ana UI iş parçacığına hemen geri dönmeniz gerekiyor? Bu sadece bir örnek." - Bir dosyadan okumak uzun bir görev olabilir, eğer dosya büyükse, UI iş parçacığını engelleyebilecek ve yanıt vermemesine neden olabilecek bir şey olabilir
Yair Nevet

Aptalca bir sorum var. Her iş parçacığının bir Kimliği vardır ve örneğin UI iş parçacığının da bir KIMLIĞI = 2 olduğunu varsayıyorum. Sonra, iş parçacığı havuzu iş parçacığındayken, şöyle bir şey yapabilir miyim: var thread = GetThread (2); thread.Execute (() => textbox1.Text = "foo")?
John

@John - Hayır, iş parçacığı zaten çalıştığı için işe yaradığını sanmıyorum. Zaten yürütülmekte olan bir iş parçacığını yürütemezsiniz. Yürütme yalnızca bir iş parçacığı çalışmadığında çalışır (IIRC)
Erik Funkenbusch

3

Kaynağa

Her iş parçacığının kendisiyle ilişkili bir bağlamı vardır - bu aynı zamanda "geçerli" bağlam olarak da bilinir - ve bu bağlamlar evreler arasında paylaşılabilir. ExecutionContext, programın yürütüldüğü geçerli ortamın veya bağlamın ilgili meta verilerini içerir. SynchronizationContext bir soyutlamayı temsil eder - uygulamanızın kodunun yürütüldüğü konumu belirtir.

SynchronizationContext, bir görevi başka bir bağlamda sıraya koymanıza olanak tanır. Her iş parçacığının kendi SynchronizatonContext'i olabileceğini unutmayın.

Örneğin: İki iş parçacığınız olduğunu varsayalım, Diş1 ve Diş2. Diyelim ki, Thread1 bazı işler yapıyor ve ardından Thread1, Thread2'de kod yürütmek istiyor. Bunu yapmanın olası bir yolu, Thread2'den SynchronizationContext nesnesini sormak, onu Thread1'e vermek ve ardından Thread1, Thread2'de kodu yürütmek için SynchronizationContext.Send'i çağırabilir.


2
Bir senkronizasyon bağlamı, belirli bir iş parçacığına bağlı olmak zorunda değildir. Birden çok iş parçacığının tek bir eşitleme bağlamına yönelik istekleri işlemesi ve tek bir iş parçacığının birden çok eşitleme bağlamı için istekleri işlemesi mümkündür.
2017'de

3

SynchronizationContext, bir UI'yi farklı bir iş parçacığından güncellememiz için bir yol sağlar (Send yöntemi aracılığıyla eşzamanlı olarak veya Post yöntemi aracılığıyla eşzamansız olarak).

Aşağıdaki örneğe bir göz atın:

    private void SynchronizationContext SyncContext = SynchronizationContext.Current;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(Work1);
        thread.Start(SyncContext);
    }

    private void Work1(object state)
    {
        SynchronizationContext syncContext = state as SynchronizationContext;
        syncContext.Post(UpdateTextBox, syncContext);
    }

    private void UpdateTextBox(object state)
    {
        Thread.Sleep(1000);
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.Text = text;
    }

SynchronizationContext.Current, UI iş parçacığının eşitleme bağlamını döndürür. Bunu nasıl bilebilirim? Her formun veya WPF uygulamasının başlangıcında, bağlam, UI iş parçacığında ayarlanacaktır. Bir WPF uygulaması oluşturur ve örneğimi çalıştırırsanız, düğmeyi tıkladığınızda yaklaşık 1 saniye uyuduğunu ve ardından dosyanın içeriğini göstereceğini göreceksiniz. UpdateTextBox yöntemini çağıran (Work1 olan) bir Thread'a geçirilen bir metot olduğundan, bunun olmayacağını bekleyebilirsiniz, bu nedenle ana UI iş parçacığı, NOPE değil, o iş parçacığı uyumalıdır! Work1 yöntemi bir iş parçacığına aktarılsa da, SyncContext olan bir nesneyi de kabul ettiğine dikkat edin. Buna bakarsanız, UpdateTextBox yönteminin Work1 yöntemi değil syncContext.Post yöntemi ile yürütüldüğünü göreceksiniz. Aşağıdakilere bir göz atın:

private void Button_Click(object sender, RoutedEventArgs e) 
{
    Thread.Sleep(1000);
    string text = File.ReadAllText(@"c:\temp\log.txt");
    myTextBox.Text = text;
}

Son örnek ve bu aynı şeyi gerçekleştiriyor. Her ikisi de işleri yaparken kullanıcı arayüzünü engellemez.

Sonuç olarak, SynchronizationContext'i bir iş parçacığı olarak düşünün. Bu bir iş parçacığı değil, bir iş parçacığı tanımlar (Tüm iş parçacığı bir SyncContext'e sahip değildir). Bir kullanıcı arayüzünü güncellemek için üzerinde Gönder veya Gönder yöntemini her çağırdığımızda, bu, kullanıcı arayüzünü normalde ana UI iş parçacığından güncellemek gibidir. Bazı nedenlerden dolayı, UI'yi farklı bir iş parçacığından güncellemeniz gerekiyorsa, iş parçacığının ana UI iş parçacığının SyncContext'e sahip olduğundan emin olun ve yürütmek istediğiniz yöntemle üzerinde Gönder veya Gönder yöntemini çağırın ve hepiniz Ayarlamak.

Umarım bu sana yardımcı olur dostum!


2

SynchronizationContext, temelde, bir programın belirli bir kod bölümünün (.Net TPL'nin bir Görev nesnesinde kapsanan) yürütülmesini tamamladıktan sonra delegelerin belirli bir yürütme bağlamında çalıştırılmasını sağlamaktan sorumlu bir geri arama temsilcisi yürütme sağlayıcısıdır .

Teknik açıdan SC, özellikle Görev Paralel Kitaplığı nesneleri için işlevini desteklemek ve sağlamak için yönlendirilmiş basit bir C # sınıfıdır.

Konsol uygulamaları haricindeki her .Net uygulaması, belirli temel çerçeveye dayalı olarak bu sınıfın belirli bir uygulamasına sahiptir, yani: WPF, WindowsForm, Asp Net, Silverlight, ecc ..

Bu nesnenin önemi, kodun eşzamansız yürütülmesinden dönen sonuçlar ile bu eşzamansız çalışmanın sonuçlarını bekleyen bağımlı kodun yürütülmesi arasındaki senkronizasyona bağlıdır.

Ve "bağlam" sözcüğü yürütme bağlamı anlamına gelir, yani bu bekleme kodunun yürütüleceği geçerli yürütme bağlamı, yani eşzamansız kod ile bekleme kodu arasındaki senkronizasyon belirli bir yürütme bağlamında gerçekleşir, dolayısıyla bu nesne SynchronizationContext olarak adlandırılır: zaman uyumsuz kodun senkronizasyonundan ve bekleyen kodun yürütülmesinden sonra bakacak yürütme bağlamını temsil eder .


1

Bu örnek Joseph Albahari'den Linqpad örneklerindendir, ancak Senkronizasyon bağlamının ne yaptığını anlamaya gerçekten yardımcı olur.

void WaitForTwoSecondsAsync (Action continuation)
{
    continuation.Dump();
    var syncContext = AsyncOperationManager.SynchronizationContext;
    new Timer (_ => syncContext.Post (o => continuation(), _)).Change (2000, -1);
}

void Main()
{
    Util.CreateSynchronizationContext();
    ("Waiting on thread " + Thread.CurrentThread.ManagedThreadId).Dump();
    for (int i = 0; i < 10; i++)
        WaitForTwoSecondsAsync (() => ("Done on thread " + Thread.CurrentThread.ManagedThreadId).Dump());
}
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.