C # 5 zaman uyumsuz desteği kullanıcı arabirimi iş parçacığı eşitleme sorunlarına nasıl yardımcı olur?


16

Bir yerde C # 5 async-await o kadar harika olacağını duydum ki bunu yapmak için endişelenmenize gerek yok:

if (InvokeRequired)
{
    BeginInvoke(...);
    return;
}
// do your stuff here

Anlaşılan bir işlemin geri araması, arayanın orijinal iş parçacığında gerçekleşecek gibi görünüyor. Eric Lippert ve Anders Hejlsberg tarafından birkaç kez bu özelliğin UI'leri (özellikle dokunmatik cihaz UI'larını) daha duyarlı hale getirme ihtiyacından kaynaklandığı belirtildi.

Böyle bir özelliğin ortak kullanımının şöyle olacağını düşünüyorum:

public class Form1 : Form
{
    // ...
    async void GetFurtherInfo()
    {
        var temperature = await GetCurrentTemperatureAsync();
        label1.Text = temperature;
    }
}

Yalnızca bir geri arama kullanılırsa, etiket metninin ayarlanması, kullanıcı arayüzünün iş parçacığında yürütülmediği için bir istisna oluşturur.

Şimdiye kadar bunun böyle olduğunu doğrulayan herhangi bir kaynak bulamadım. Bunu bilen var mı? Bunun nasıl çalışacağını teknik olarak açıklayan herhangi bir belge var mı?

Lütfen güvenilir bir kaynaktan bir bağlantı sağlayın, sadece "evet" yanıtı vermeyin.


En azından awaitişlevsellik söz konusu olduğunda , bu pek olası görünmemektedir . Sadece için sözdizimsel şeker bir sürü devamı geçmesiyle . Muhtemelen WinForms'a yardımcı olması gereken ilgisiz bazı iyileştirmeler var mı? Bu, .NET çerçevesi altında olsa da, özellikle C # değil.
Aaronaught

@Aaronaught katılıyorum, bu yüzden soruyu tam olarak soruyorum. Nereden geldiğimi netleştirmek için soruyu düzenledim. Bu özelliği oluşturmaları garip geliyor ve yine de rezil InvokeRequired kod stilini kullanmamızı gerektiriyor.
Alex

Yanıtlar:


17

Sanırım burada birkaç şey karıştı. İstediğiniz şey zaten kullanılabilir System.Threading.Tasks, asyncve awaitC # 5'de aynı özellik için biraz daha hoş bir sözdizim şekeri sağlayacaksınız.

Bir Winforms örneği kullanalım - forma bir düğme ve bir metin kutusu bırakın ve şu kodu kullanın:

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => DelayedAdd(5, 10))
        .ContinueWith(t => DelayedAdd(t.Result, 20))
        .ContinueWith(t => DelayedAdd(t.Result, 30))
        .ContinueWith(t => DelayedAdd(t.Result, 50))
        .ContinueWith(t => textBox1.Text = t.Result.ToString(),
            TaskScheduler.FromCurrentSynchronizationContext());
}

private int DelayedAdd(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Çalıştırın ve o (a) UI iş parçacığı ve (b) Hata zamanki "geçerli değil çapraz iplik operasyonu" alamadım engellemez göreceksiniz - Kaldırmak sürece TaskSchedulerson gelen argüman ContinueWith, içinde hangi durumda olacak.

Bu bataklık standart devam geçiş tarzı . Sihir TaskSchedulersınıfta ve özellikle tarafından alınan örnekte gerçekleşir FromCurrentSynchronizationContext. Bunu herhangi bir sürdürmeye geçirin ve devam etmenin FromCurrentSynchronizationContextyöntem olarak adlandırılan herhangi bir iş parçacığında (bu durumda UI iş parçacığı) çalışması gerektiğini söylersiniz .

Bekleyenler, hangi iş parçacığı üzerinde başladıklarının ve hangi iş parçacığının gerçekleşmesi gerektiğinin farkında olmaları açısından biraz daha karmaşıktır. Yukarıdaki kod biraz daha doğal olarak yazılabilir :

private async void button1_Click(object sender, EventArgs e)
{
    int a = await DelayedAddAsync(5, 10);
    int b = await DelayedAddAsync(a, 20);
    int c = await DelayedAddAsync(b, 30);
    int d = await DelayedAddAsync(c, 50);
    textBox1.Text = d.ToString();
}

private async Task<int> DelayedAddAsync(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

Bu iki çok benzer görünmelidir ve aslında onlar vardır çok benzer. DelayedAddAsyncYöntemi şimdi bir döner Task<int>yerine bölgesinin intve böylece awaitsadece bu her biri üzerine devamlılık tokat. Temel fark, her satırdaki senkronizasyon bağlamından geçmesidir, bu nedenle bunu son örnekte yaptığımız gibi açıkça yapmanız gerekmez.

Teoride farklılıklar çok daha önemlidir. İkinci örnekte, button1_Clickyöntemdeki her satır aslında UI iş parçacığında yürütülür, ancak görevin kendisi ( DelayedAddAsync) arka planda çalışır. İlk örnekte, her şey çalışır içinde arka , hariç için atama için textBox1.Textbiz açıkça UI parçacığının senkronizasyon bağlamına bağlı olduğunuz.

Bu gerçekten ilginç olan şey await- bir bekleyicinin herhangi bir engelleme çağrısı olmadan aynı yönteme girip çıkabilmesi . Siz çağırırsınız await, mevcut iş parçacığı iletileri işlemeye geri döner ve bittiğinde, bekçi tam olarak kaldığı yerden, bıraktığı aynı iş parçacığında alır. Ama sorudaki Invoke/ BeginInvokekontrastınız açısından, ' Bunu uzun zaman önce bırakmış olmanız gerektiğini söylediğim için üzgünüm.


Bu çok ilginç @Aaronaught. Devam tarzının farkındaydım, ancak tüm bu "senkronizasyon bağlamı" öğesinin farkında değildim. Bu senkronizasyon bağlamını C # 5 zaman uyumsuz beklemesine bağlayan bir belge var mı? Mevcut bir özellik olduğunu anlıyorum, ancak varsayılan olarak kullandıkları gerçeği büyük bir şey gibi geliyor, özellikle performansta bazı büyük etkileri olması gerekiyor, değil mi? Bununla ilgili başka yorumlarınız var mı? Bu arada cevabınız için teşekkürler.
Alex

1
@Alex: Tüm bu takip sorularının yanıtları için Async Performansı: Async ve Beklemenin Maliyetlerini Anlama'yı okumanızı öneririz . "Bağlam Hakkında Bakım" bölümünde, her şeyin senkronizasyon bağlamıyla nasıl bir ilişkisi olduğu açıklanmaktadır.
Aaronaught

(Bu arada, senkronizasyon bağlamları yeni değil; 2.0'dan beri çerçeve içerisindeler. TPL onları daha kolay kullanıyordu.)
Aaronaught

2
Neden hala InvokeRequired stilini kullanma üzerine tartışmalar olduğunu merak ediyorum ve gördüğüm konuların çoğu senkronizasyon bağlamından bile bahsetmiyor. Bu soruyu
Alex

2
@Alex: Sanırım doğru yerlere bakmıyordunuz . Sana ne söyleyeceğimi bilmiyorum; .NET topluluğunun yakalanması uzun süren büyük bölümleri vardır. Cehennem, hala ArrayListyeni kodda sınıfı kullanan bazı kodlayıcılar görüyorum . RX ile neredeyse hiç deneyimim yok, kendim. İnsanlar, bildikleri şey güncel değilse bile, bildiklerini ve bildiklerini paylaşmaları gerektiğini öğrenir. Bu cevap birkaç yıl içinde güncelliğini yitirmiş olabilir.
Aaronaught

4

Evet, UI iş parçacığı için, arayanın orijinal iş parçacığında bir bekleme işleminin geri çağrısı gerçekleşir.

Eric Lippert bir yıl önce 8 bölümlük bir dizi yazdı: Muhteşem Kodlar

DÜZENLEME: işte Anders'in // derlemesi / sunumu: kanal9

BTW, "// build /" baş aşağı çevirirseniz, "/ plinq //" ;-) olduğunu fark ettiniz mi?


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.