İş Parçacığı Modeli

Windows Presentation Foundation (WPF), geliştiricileri iş parçacığı açma güçlüklerinden kurtarmak için tasarlanmıştır. Sonuç olarak, WPF geliştiricilerinin çoğunluğu birden fazla iş parçacığı kullanan bir arabirim yazmak zorunda olmayacaktır. Çok iş parçacıklı programlar karmaşık olduğundan ve hata ayıklaması zor olduğundan, tek iş parçacıklı çözümler mevcut olduğunda bu programlardan kaçınılmalıdır.

Ancak ne kadar iyi bir mimariye sahip olursa olsun hiçbir kullanıcı arabirimi çerçevesi, her türlü sorun için tek iş parçacıklı bir çözüm sağlayabilecektir. WPF yakında gelir, ancak yine de birden çok iş parçacığının kullanıcı arabirimi (UI) yanıt hızını veya uygulama performansını artıran durumlar vardır. Bu yazıda bazı arka plan malzemeleri ele geçirildikten sonra bu durumlardan bazıları incelenecek ve ardından bazı alt düzey ayrıntıların tartıştığı sonucuna varacağız.

Not

Bu konu, zaman uyumsuz çağrılar BeginInvoke için yöntemini kullanarak iş parçacığını açıklamaz. Bir veya parametresini alan yöntemini çağırarak InvokeAsync da zaman uyumsuz ActionFunc<TResult> çağrılar da ebilirsiniz. yöntemi, InvokeAsync özelliği olan bir veya DispatcherOperationDispatcherOperation<TResult>Task döndürür. anahtar sözcüğünü await veya ilişkili ile DispatcherOperationTask kullanabilirsiniz. veya tarafından döndürülen için zaman uyumlu Task olarak beklemeniz gerekirse DispatcherOperationDispatcherOperation<TResult> uzantı yöntemini DispatcherOperationWait çağırabilirsiniz. çağrısı Task.Wait kilitlenmeye neden olur. Zaman uyumsuz işlemler gerçekleştirmek için kullanma Task hakkında daha fazla bilgi için Task yönteminde Invoke ayrıca parametre olarak veya alan aşırı ActionFunc<TResult> yüklemeler de vardır. bir temsilci Invoke veya geçerek zaman uyumlu çağrılar yapmak için yöntemini ActionFunc<TResult> kullanabilirsiniz.

Genel Bakış ve Dağıtıcı

WPF uygulamaları genellikle iki iş parçacığıyla başlar: biri işleme için, diğeri kullanıcı arabirimini yönetmek için. ui iş parçacığı girişi alırken, olayları işler, ekranı boyarken ve uygulama kodunu çalıştırırken işleme iş parçacığı arka planda gizli olarak çalışır. Çoğu uygulama tek bir kullanıcı arabirimi iş parçacığı kullanır, ancak bazı durumlarda birkaç kullanıcı arabirimi kullanmak en iyisidir. Bunu daha sonra bir örnekle ele aacağız.

UI iş parçacığı, iş öğelerini adlı bir nesnenin içinde kuyruğa Dispatcher almaktadır. , Dispatcher iş öğelerini öncelik temelinde seçer ve tamamlanması için her birini çalıştırır. Her kullanıcı arabirimi iş parçacığı en az bir ' ye sahip Dispatcher olmalı ve her biri iş öğelerini tam olarak bir iş Dispatcher parçacığında yürütebilir.

Duyarlı, kullanıcı dostu uygulamalar inşa etmek, iş öğelerini küçük Dispatcher tutarak aktarım hızını en üst düzeye çıkarmaktır. Bu şekilde öğeler, kuyrukta iş için beklerken Dispatcher hiçbir zaman eskir. Giriş ve yanıt arasındaki herhangi bir fark edilebilir gecikme, bir kullanıcıyı hayal kırıklığına uğratabilir.

WPF uygulamalarının büyük işlemleri nasıl işlemesi gerekir? Kodunuz büyük bir hesaplama içeriyorsa veya uzak bir sunucu üzerinde veritabanını sorgulaması gerekirse ne olur? Genellikle, yanıt büyük işlemi ayrı bir iş parçacığında işlemek ve kullanıcı arabirimi iş parçacığını kuyrukta yer alan öğelere eğilimli olacak şekilde serbest Dispatcher bırakmak olur. Büyük işlem tamamlandığında, sonucu görüntülenmek üzere kullanıcı arabirimi iş parçacığına geri bildirebilir.

Geçmişte Windows kullanıcı arabirimi öğelerine yalnızca onları oluşturan iş parçacığı tarafından erişilsin. Bu, uzun süre çalışan görevlerden sorumlu bir arka plan iş parçacığının tamamlandığında metin kutusunu güncelleştiremeyecekleri anlamına gelir. Windows ui bileşenlerinin bütünlüğünü sağlamak için bunu yapar. İçindekiler tablo sırasında arka plan iş parçacığı tarafından güncelleştirilmişse liste kutusu garip görünür.

WPF, bu koordinasyonu zorlarken yerleşik bir karşılıklı dışlama mekanizmasına sahiptir. WPF'de çoğu sınıf, 'den DispatcherObject türetmektedir. Bir yapıda, DispatcherObject o anda çalışan iş Dispatcher parçacığına bağlı başvuru depolar. Aslında, onu DispatcherObject oluşturan iş parçacığı ile ilişkilendirin. Program yürütme sırasında, DispatcherObject bir genel yöntemini VerifyAccess çağırabilirsiniz. VerifyAccess geçerli iş Dispatcher parçacığıyla ilişkili olan iş parçacığını inceler ve bunu, yapı sırasında Dispatcher depolanan başvuruyla karşılar. Eşleşmezse bir VerifyAccess özel durum oluşturur. VerifyAccess , bir 'e ait olan her yöntemin başında çağrılma amacına DispatcherObject yöneliktir.

Kullanıcı arabirimini yalnızca bir iş parçacığı değiştirebilirse, arka plan iş parçacıkları kullanıcıyla nasıl etkileşime olabilir? Arka plan iş parçacığı, kullanıcı arabirimi iş parçacığının kendi adına bir işlem gerçekleştirmesini sorabilir. Bunu, bir iş öğesini kullanıcı arabirimi iş Dispatcher parçacığının öğesiyle kaydederek yapar. sınıfı, Dispatcher iş öğelerini kaydetmek için iki yöntem sağlar: ve InvokeBeginInvoke . Her iki yöntem de yürütme için bir temsilci zamanlar. Invoke zaman uyumlu bir çağrıdır; başka bir ifadeyle, kullanıcı arabirimi iş parçacığı temsilciyi yürütmeyi tamamlayana kadar geri dönmez. BeginInvoke zaman uyumsuz olur ve hemen döndürür.

, Dispatcher kuyrukta yer alan öğeleri önceliğe göre sıralar. Kuyruğa öğe eklerken belirtilebilir on düzey Dispatcher vardır. Bu öncelikler, DispatcherPriority numaralamada korunur. Düzeyler hakkında DispatcherPriority ayrıntılı bilgi, Windows SDK belgelerinde bulunabilir.

İş Parçacıkları Çalışır: Örnekler

Single-Threaded Hesaplaması ile Long-Running Uygulaması

Çoğu grafik kullanıcı arabirimi (GUI), kullanıcı etkileşimlerine yanıt olarak oluşturulan olayları beklerken zamanlarının büyük bir kısmını boşta geçirir. Dikkatli programlama ile bu boşta kalma süresi, kullanıcı arabiriminin yanıt hızını etkilemeden yapıcı bir şekilde kullanılabilir. WPF iş parçacığı modeli, girişin ui iş parçacığında bir işlemi kesintiye uğratmasına izin vermez. Bu, bekleyen giriş olaylarını eskimeden önce düzenli aralıklarla Dispatcher işlemeye devam etmek için geri dönmeleri gerektiğini anlamına gelir.

Aşağıdaki örneği inceleyin:

Screenshot that shows threading of prime numbers.

Bu basit uygulama, asal sayıları arayarak üçten yukarı doğru sayar. Kullanıcı Başlat düğmesine tıkladığında arama başlar. Program bir asal bulduğunda, bulma ile kullanıcı arabirimini günceller. Herhangi bir noktada kullanıcı aramayı durdurabilir.

Yeterince basit olsa da, asal sayı araması sonsuza kadar devam eder ve bu da bazı zorluklara neden olur. Düğmenin tıklama olayı işleyicisi içindeki aramanın tamamını ele aldıy olsaydık, kullanıcı arabirimi iş parçacığına hiçbir zaman diğer olayları işleme şansı vermeziz. Kullanıcı arabirimi giriş veya işlem iletilerine yanıt veremiyor olabilir. Hiçbir zaman yeniden boyanmaz ve düğme tıklamalarına yanıt vermez.

Asal sayı aramalarını ayrı bir iş parçacığında yürütebilirsiniz, ancak ardından eşitleme sorunlarıyla ilgilenebilirsiniz. Tek iş parçacıklı bir yaklaşımla, bulunan en büyük asal listelenmiş etiketi doğrudan güncelleştirebilirsiniz.

Hesaplama görevini yönetilebilir öbeklere parçalarsanız, ve olaylarına düzenli aralıklarla Dispatcher geri dönebilirsiniz. WPF'ye girişi yeniden boyatma ve işleme fırsatı ve vermemiz gerekir.

hesaplama ve olay işleme arasında işleme süresi bölmenin en iyi yolu, hesaplamayı 'den Dispatcher yönetmektir. yöntemini BeginInvoke kullanarak, kullanıcı arabirimi olaylarını çeken aynı kuyrukta asal sayı denetimleri zamanlarız. Örneğimizde aynı anda yalnızca tek bir asal sayı denetimi zamanlarız. Asal sayı denetimi tamamlandıktan sonra bir sonraki denetimi hemen zamanlarız. Bu denetim yalnızca bekleyen kullanıcı arabirimi olayları işledikten sonra devam eder.

Screenshot that shows the dispatcher queue.

Microsoft Word bu mekanizmayı kullanarak yazım denetimi gerçekleştirebilir. Yazım denetimi, kullanıcı arabirimi iş parçacığının boşta kalma süresi kullanılarak arka planda yapılır. Şimdi koda göz atabilirsiniz.

Aşağıdaki örnek, kullanıcı arabirimini oluşturan XAML'i gösterir.

<Window x:Class="SDKSamples.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Prime Numbers" Width="260" Height="75"
    >
  <StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
    <Button Content="Start"  
            Click="StartOrStop"
            Name="startStopButton"
            Margin="5,0,5,0"
            />
    <TextBlock Margin="10,5,0,0">Biggest Prime Found:</TextBlock>
    <TextBlock Name="bigPrime" Margin="4,5,0,0">3</TextBlock>
  </StackPanel>
</Window>

Aşağıdaki örnek, arka arkasındaki kodu gösterir.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Threading;

namespace SDKSamples
{
    public partial class Window1 : Window
    {
        public delegate void NextPrimeDelegate();

        //Current number to check
        private long num = 3;

        private bool continueCalculating = false;

        public Window1() : base()
        {
            InitializeComponent();
        }

        private void StartOrStop(object sender, EventArgs e)
        {
            if (continueCalculating)
            {
                continueCalculating = false;
                startStopButton.Content = "Resume";
            }
            else
            {
                continueCalculating = true;
                startStopButton.Content = "Stop";
                startStopButton.Dispatcher.BeginInvoke(
                    DispatcherPriority.Normal,
                    new NextPrimeDelegate(CheckNextNumber));
            }
        }

        public void CheckNextNumber()
        {
            // Reset flag.
            NotAPrime = false;

            for (long i = 3; i <= Math.Sqrt(num); i++)
            {
                if (num % i == 0)
                {
                    // Set not a prime flag to true.
                    NotAPrime = true;
                    break;
                }
            }

            // If a prime number.
            if (!NotAPrime)
            {
                bigPrime.Text = num.ToString();
            }

            num += 2;
            if (continueCalculating)
            {
                startStopButton.Dispatcher.BeginInvoke(
                    System.Windows.Threading.DispatcherPriority.SystemIdle,
                    new NextPrimeDelegate(this.CheckNextNumber));
            }
        }

        private bool NotAPrime = false;
    }
}
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Threading
Imports System.Threading

Namespace SDKSamples
    Partial Public Class MainWindow
        Inherits Window
        Public Delegate Sub NextPrimeDelegate()

        'Current number to check 
        Private num As Long = 3

        Private continueCalculating As Boolean = False

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs)
            If continueCalculating Then
                continueCalculating = False
                startStopButton.Content = "Resume"
            Else
                continueCalculating = True
                startStopButton.Content = "Stop"
                startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber))
            End If
        End Sub

        Public Sub CheckNextNumber()
            ' Reset flag.
            NotAPrime = False

            For i As Long = 3 To Math.Sqrt(num)
                If num Mod i = 0 Then
                    ' Set not a prime flag to true.
                    NotAPrime = True
                    Exit For
                End If
            Next

            ' If a prime number.
            If Not NotAPrime Then
                bigPrime.Text = num.ToString()
            End If

            num += 2
            If continueCalculating Then
                startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber))
            End If
        End Sub

        Private NotAPrime As Boolean = False
    End Class
End Namespace

Aşağıdaki örnek için olay işleyicisini Button gösterir.

private void StartOrStop(object sender, EventArgs e)
{
    if (continueCalculating)
    {
        continueCalculating = false;
        startStopButton.Content = "Resume";
    }
    else
    {
        continueCalculating = true;
        startStopButton.Content = "Stop";
        startStopButton.Dispatcher.BeginInvoke(
            DispatcherPriority.Normal,
            new NextPrimeDelegate(CheckNextNumber));
    }
}
Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs)
    If continueCalculating Then
        continueCalculating = False
        startStopButton.Content = "Resume"
    Else
        continueCalculating = True
        startStopButton.Content = "Stop"
        startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber))
    End If
End Sub

üzerinde metni güncelleştirmenin yanı sıra, bu işleyici kuyruğa bir temsilci ekleyerek ilk asal Button sayı denetimlerini zamanlama sorumluluğundadır. Dispatcher Bu olay işleyicisi çalışması tamamlandıktan bir süre Dispatcher sonra, yürütme için bu temsilciyi seçer.

Daha önce de belirttiğimiz BeginInvoke gibi, Dispatcher yürütme için bir temsilci zamanlaması için kullanılan üyedir. Bu durumda önceliği SystemIdle seçeriz. , Dispatcher bu temsilciyi yalnızca işlecek önemli olaylar olduğunda yürütür. Kullanıcı arabirimi yanıt hızı, sayı denetiminden daha önemlidir. Ayrıca sayı denetimi yordamını temsil eden yeni bir temsilci de iletiriz.

public void CheckNextNumber()
{
    // Reset flag.
    NotAPrime = false;

    for (long i = 3; i <= Math.Sqrt(num); i++)
    {
        if (num % i == 0)
        {
            // Set not a prime flag to true.
            NotAPrime = true;
            break;
        }
    }

    // If a prime number.
    if (!NotAPrime)
    {
        bigPrime.Text = num.ToString();
    }

    num += 2;
    if (continueCalculating)
    {
        startStopButton.Dispatcher.BeginInvoke(
            System.Windows.Threading.DispatcherPriority.SystemIdle,
            new NextPrimeDelegate(this.CheckNextNumber));
    }
}

private bool NotAPrime = false;
Public Sub CheckNextNumber()
    ' Reset flag.
    NotAPrime = False

    For i As Long = 3 To Math.Sqrt(num)
        If num Mod i = 0 Then
            ' Set not a prime flag to true.
            NotAPrime = True
            Exit For
        End If
    Next

    ' If a prime number.
    If Not NotAPrime Then
        bigPrime.Text = num.ToString()
    End If

    num += 2
    If continueCalculating Then
        startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber))
    End If
End Sub

Private NotAPrime As Boolean = False

Bu yöntem, sonraki tek say ın asal olup olduğunu denetler. asal ise yöntemi, bulmasını yansıtacak bigPrimeTextBlock şekilde doğrudan yöntemini günceller. Hesaplama, bileşeni oluşturmak için kullanılan iş parçacığında oluştuğu için bunu yapabiliriz. Hesaplama için ayrı bir iş parçacığı kullanmayı seçtiy olsaydık, daha karmaşık bir eşitleme mekanizması kullanmamız ve güncelleştirmeyi kullanıcı arabirimi iş parçacığında yürütmemiz gerekirdi. Sonraki adımlarda bu durumu gösteriz.

Bu örneğe ilişkin tam kaynak kodu için, Long-Running Hesaplama örneği Ile tek Iş parçacıklı uygulamaya bakın

Bir arka plan Iş parçacığı ile engelleyici Işlemi işleme

Grafik uygulamada engelleme işlemlerini işleme zor olabilir. Uygulama dondurmak için göründüğünden olay işleyicilerinden engelleme yöntemlerini çağırmak istemiyorum. Bu işlemleri işlemek için ayrı bir iş parçacığı kullanabiliriz, ancak bu işlem tamamlandığında, GUI 'yi çalışan iş parçacığından doğrudan değiştireemdiğimiz için UI iş parçacığı ile eşitliyoruz. InvokeBeginInvoke UI iş parçacığının içine temsilciler eklemek için veya kullanabilirsiniz Dispatcher . Sonuç olarak, bu Temsilciler Kullanıcı arabirimi öğelerini değiştirme izniyle yürütülür.

Bu örnekte, hava durumu tahminini alan bir uzak yordam çağrısını taklit ediyoruz. Bu çağrıyı yürütmek için ayrı bir çalışan iş parçacığı kullanıyoruz ve Dispatcher bitirtiğimiz sırada UI iş parçacığında bir güncelleştirme yöntemi zamanlamamız gerekir.

Screenshot that shows the weather UI.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Threading;

namespace SDKSamples
{
    public partial class Window1 : Window
    {
        // Delegates to be used in placking jobs onto the Dispatcher.
        private delegate void NoArgDelegate();
        private delegate void OneArgDelegate(String arg);

        // Storyboards for the animations.
        private Storyboard showClockFaceStoryboard;
        private Storyboard hideClockFaceStoryboard;
        private Storyboard showWeatherImageStoryboard;
        private Storyboard hideWeatherImageStoryboard;

        public Window1(): base()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Load the storyboard resources.
            showClockFaceStoryboard =
                (Storyboard)this.Resources["ShowClockFaceStoryboard"];
            hideClockFaceStoryboard =
                (Storyboard)this.Resources["HideClockFaceStoryboard"];
            showWeatherImageStoryboard =
                (Storyboard)this.Resources["ShowWeatherImageStoryboard"];
            hideWeatherImageStoryboard =
                (Storyboard)this.Resources["HideWeatherImageStoryboard"];
        }

        private void ForecastButtonHandler(object sender, RoutedEventArgs e)
        {
            // Change the status image and start the rotation animation.
            fetchButton.IsEnabled = false;
            fetchButton.Content = "Contacting Server";
            weatherText.Text = "";
            hideWeatherImageStoryboard.Begin(this);

            // Start fetching the weather forecast asynchronously.
            NoArgDelegate fetcher = new NoArgDelegate(
                this.FetchWeatherFromServer);

            fetcher.BeginInvoke(null, null);
        }

        private void FetchWeatherFromServer()
        {
            // Simulate the delay from network access.
            Thread.Sleep(4000);

            // Tried and true method for weather forecasting - random numbers.
            Random rand = new Random();
            String weather;

            if (rand.Next(2) == 0)
            {
                weather = "rainy";
            }
            else
            {
                weather = "sunny";
            }

            // Schedule the update function in the UI thread.
            tomorrowsWeather.Dispatcher.BeginInvoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new OneArgDelegate(UpdateUserInterface),
                weather);
        }

        private void UpdateUserInterface(String weather)
        {
            //Set the weather image
            if (weather == "sunny")
            {
                weatherIndicatorImage.Source = (ImageSource)this.Resources[
                    "SunnyImageSource"];
            }
            else if (weather == "rainy")
            {
                weatherIndicatorImage.Source = (ImageSource)this.Resources[
                    "RainingImageSource"];
            }

            //Stop clock animation
            showClockFaceStoryboard.Stop(this);
            hideClockFaceStoryboard.Begin(this);

            //Update UI text
            fetchButton.IsEnabled = true;
            fetchButton.Content = "Fetch Forecast";
            weatherText.Text = weather;
        }

        private void HideClockFaceStoryboard_Completed(object sender,
            EventArgs args)
        {
            showWeatherImageStoryboard.Begin(this);
        }

        private void HideWeatherImageStoryboard_Completed(object sender,
            EventArgs args)
        {
            showClockFaceStoryboard.Begin(this, true);
        }
    }
}

Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Media.Imaging
Imports System.Windows.Shapes
Imports System.Windows.Threading
Imports System.Threading

Namespace SDKSamples
    Partial Public Class Window1
        Inherits Window
        ' Delegates to be used in placking jobs onto the Dispatcher.
        Private Delegate Sub NoArgDelegate()
        Private Delegate Sub OneArgDelegate(ByVal arg As String)

        ' Storyboards for the animations.
        Private showClockFaceStoryboard As Storyboard
        Private hideClockFaceStoryboard As Storyboard
        Private showWeatherImageStoryboard As Storyboard
        Private hideWeatherImageStoryboard As Storyboard

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub Window_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Load the storyboard resources.
            showClockFaceStoryboard = CType(Me.Resources("ShowClockFaceStoryboard"), Storyboard)
            hideClockFaceStoryboard = CType(Me.Resources("HideClockFaceStoryboard"), Storyboard)
            showWeatherImageStoryboard = CType(Me.Resources("ShowWeatherImageStoryboard"), Storyboard)
            hideWeatherImageStoryboard = CType(Me.Resources("HideWeatherImageStoryboard"), Storyboard)
        End Sub

        Private Sub ForecastButtonHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Change the status image and start the rotation animation.
            fetchButton.IsEnabled = False
            fetchButton.Content = "Contacting Server"
            weatherText.Text = ""
            hideWeatherImageStoryboard.Begin(Me)

            ' Start fetching the weather forecast asynchronously.
            Dim fetcher As New NoArgDelegate(AddressOf Me.FetchWeatherFromServer)

            fetcher.BeginInvoke(Nothing, Nothing)
        End Sub

        Private Sub FetchWeatherFromServer()
            ' Simulate the delay from network access.
            Thread.Sleep(4000)

            ' Tried and true method for weather forecasting - random numbers.
            Dim rand As New Random()
            Dim weather As String

            If rand.Next(2) = 0 Then
                weather = "rainy"
            Else
                weather = "sunny"
            End If

            ' Schedule the update function in the UI thread.
            tomorrowsWeather.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, New OneArgDelegate(AddressOf UpdateUserInterface), weather)
        End Sub

        Private Sub UpdateUserInterface(ByVal weather As String)
            'Set the weather image
            If weather = "sunny" Then
                weatherIndicatorImage.Source = CType(Me.Resources("SunnyImageSource"), ImageSource)
            ElseIf weather = "rainy" Then
                weatherIndicatorImage.Source = CType(Me.Resources("RainingImageSource"), ImageSource)
            End If

            'Stop clock animation
            showClockFaceStoryboard.Stop(Me)
            hideClockFaceStoryboard.Begin(Me)

            'Update UI text
            fetchButton.IsEnabled = True
            fetchButton.Content = "Fetch Forecast"
            weatherText.Text = weather
        End Sub

        Private Sub HideClockFaceStoryboard_Completed(ByVal sender As Object, ByVal args As EventArgs)
            showWeatherImageStoryboard.Begin(Me)
        End Sub

        Private Sub HideWeatherImageStoryboard_Completed(ByVal sender As Object, ByVal args As EventArgs)
            showClockFaceStoryboard.Begin(Me, True)
        End Sub
    End Class
End Namespace

Aşağıda, belirtime bazı ayrıntılar verilmiştir.

  • Düğme Işleyicisi oluşturma

    private void ForecastButtonHandler(object sender, RoutedEventArgs e)
    {
        // Change the status image and start the rotation animation.
        fetchButton.IsEnabled = false;
        fetchButton.Content = "Contacting Server";
        weatherText.Text = "";
        hideWeatherImageStoryboard.Begin(this);
    
        // Start fetching the weather forecast asynchronously.
        NoArgDelegate fetcher = new NoArgDelegate(
            this.FetchWeatherFromServer);
    
        fetcher.BeginInvoke(null, null);
    }
    
    Private Sub ForecastButtonHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Change the status image and start the rotation animation.
        fetchButton.IsEnabled = False
        fetchButton.Content = "Contacting Server"
        weatherText.Text = ""
        hideWeatherImageStoryboard.Begin(Me)
    
        ' Start fetching the weather forecast asynchronously.
        Dim fetcher As New NoArgDelegate(AddressOf Me.FetchWeatherFromServer)
    
        fetcher.BeginInvoke(Nothing, Nothing)
    End Sub
    

Düğmeye tıklandığında saat çizimi görüntülenir ve canlandırarak başlayın. Düğmeyi devre dışı bıraktık. FetchWeatherFromServerYöntemi yeni bir iş parçacığında çağırır ve sonra geri döntiğimiz için Dispatcher Hava durumu tahminini toplamayı beklerken olayları işlemesini sağlar.

  • Hava durumu getiriliyor

    private void FetchWeatherFromServer()
    {
        // Simulate the delay from network access.
        Thread.Sleep(4000);
    
        // Tried and true method for weather forecasting - random numbers.
        Random rand = new Random();
        String weather;
    
        if (rand.Next(2) == 0)
        {
            weather = "rainy";
        }
        else
        {
            weather = "sunny";
        }
    
        // Schedule the update function in the UI thread.
        tomorrowsWeather.Dispatcher.BeginInvoke(
            System.Windows.Threading.DispatcherPriority.Normal,
            new OneArgDelegate(UpdateUserInterface),
            weather);
    }
    
    Private Sub FetchWeatherFromServer()
        ' Simulate the delay from network access.
        Thread.Sleep(4000)
    
        ' Tried and true method for weather forecasting - random numbers.
        Dim rand As New Random()
        Dim weather As String
    
        If rand.Next(2) = 0 Then
            weather = "rainy"
        Else
            weather = "sunny"
        End If
    
        ' Schedule the update function in the UI thread.
        tomorrowsWeather.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, New OneArgDelegate(AddressOf UpdateUserInterface), weather)
    End Sub
    

Şeyleri basit tutmak için, bu örnekte aslında hiçbir ağ kodu yoktur. Bunun yerine, yeni iş parçacığını dört saniye boyunca uykuya yerleştirerek ağ erişimi gecikmesi benzetimi yapılır. Bu süre içinde, özgün UI iş parçacığı çalışmaya devam eder ve olaylara yanıt verir. Bunu göstermek için, çalışan bir animasyon kalmadı ve simge durumuna küçült ve büyüt düğmeleri de çalışmaya devam eder.

Gecikme bittiğinde ve hava durumu tahminimizi gelişigüzel bir şekilde seçtiğimiz zaman, Kullanıcı arabirimi iş parçacığına yeniden rapor vereceğiz. Bunu UpdateUserInterface , Kullanıcı arabirimi iş parçacığında ilgili iş parçacığını kullanarak bir çağrı zamanlayarak yapacağız Dispatcher . Bu zamanlanmış yöntem çağrısına hava durumunu açıklayan bir dize geçiririz.

  • Kullanıcı arabirimini güncelleştirme

    private void UpdateUserInterface(String weather)
    {
        //Set the weather image
        if (weather == "sunny")
        {
            weatherIndicatorImage.Source = (ImageSource)this.Resources[
                "SunnyImageSource"];
        }
        else if (weather == "rainy")
        {
            weatherIndicatorImage.Source = (ImageSource)this.Resources[
                "RainingImageSource"];
        }
    
        //Stop clock animation
        showClockFaceStoryboard.Stop(this);
        hideClockFaceStoryboard.Begin(this);
    
        //Update UI text
        fetchButton.IsEnabled = true;
        fetchButton.Content = "Fetch Forecast";
        weatherText.Text = weather;
    }
    
    Private Sub UpdateUserInterface(ByVal weather As String)
        'Set the weather image
        If weather = "sunny" Then
            weatherIndicatorImage.Source = CType(Me.Resources("SunnyImageSource"), ImageSource)
        ElseIf weather = "rainy" Then
            weatherIndicatorImage.Source = CType(Me.Resources("RainingImageSource"), ImageSource)
        End If
    
        'Stop clock animation
        showClockFaceStoryboard.Stop(Me)
        hideClockFaceStoryboard.Begin(Me)
    
        'Update UI text
        fetchButton.IsEnabled = True
        fetchButton.Content = "Fetch Forecast"
        weatherText.Text = weather
    End Sub
    

DispatcherKullanıcı arabirimi iş parçacığında zaman olduğunda, zamanlanan çağrısını yürütür UpdateUserInterface . Bu yöntem saat animasyonunu durdurup hava durumunu tanımlayacak bir görüntü seçer. Bu görüntüyü görüntüler ve "tahmin getirme" düğmesini geri yükler.

birden çok Windows, birden çok iş parçacığı

Bazı WPF uygulamaları birden çok üst düzey pencere gerektirir. Birden çok pencere yönetmek için bir Iş parçacığı/birleşim için mükemmel bir kabul edilebilir Dispatcher , ancak bazen birkaç iş parçacığı daha iyi bir iş olur. Bu durum özellikle, bir Windows 'un iş parçacığını tekeline alacak herhangi bir şansınız varsa geçerlidir.

Windows Explorer bu biçimde çalışmaktadır. Her yeni Gezgin penceresi orijinal işleme aittir, ancak bağımsız bir iş parçacığının denetimi altında oluşturulur.

WPF Frame denetimini kullanarak Web sayfalarını görüntüleriz. Kolayca basit bir Internet Explorer yerine kolayca oluşturabiliyoruz. Önemli bir özellik ile başlıyoruz: yeni bir Gezgin penceresi açma özelliği. Kullanıcı "yeni pencere" düğmesine tıkladığında, ayrı bir iş parçacığında penceremizin kopyasını başladık. Bu şekilde, Windows 'un birindeki uzun süreli veya engelleyici işlemler diğer tüm pencereleri kilitlemez.

Gerçekte, Web tarayıcısı modelinin karmaşık iş parçacığı modeli vardır. Çoğu okuyucuları tanımak gerektiğinden, bunu seçtik.

Aşağıdaki örnek kodu gösterir.

<Window x:Class="SDKSamples.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MultiBrowse"
    Height="600" 
    Width="800"
    Loaded="OnLoaded"
    >
  <StackPanel Name="Stack" Orientation="Vertical">
    <StackPanel Orientation="Horizontal">
      <Button Content="New Window"
              Click="NewWindowHandler" />
      <TextBox Name="newLocation"
               Width="500" />
      <Button Content="GO!"
              Click="Browse" />
    </StackPanel>

    <Frame Name="placeHolder"
            Width="800"
            Height="550"></Frame>
  </StackPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Threading;
using System.Threading;

namespace SDKSamples
{
    public partial class Window1 : Window
    {

        public Window1() : base()
        {
            InitializeComponent();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
           placeHolder.Source = new Uri("http://www.msn.com");
        }

        private void Browse(object sender, RoutedEventArgs e)
        {
            placeHolder.Source = new Uri(newLocation.Text);
        }

        private void NewWindowHandler(object sender, RoutedEventArgs e)
        {
            Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
            newWindowThread.SetApartmentState(ApartmentState.STA);
            newWindowThread.IsBackground = true;
            newWindowThread.Start();
        }

        private void ThreadStartingPoint()
        {
            Window1 tempWindow = new Window1();
            tempWindow.Show();
            System.Windows.Threading.Dispatcher.Run();
        }
    }
}

Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Threading
Imports System.Threading


Namespace SDKSamples
    Partial Public Class Window1
        Inherits Window

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
           placeHolder.Source = New Uri("http://www.msn.com")
        End Sub

        Private Sub Browse(ByVal sender As Object, ByVal e As RoutedEventArgs)
            placeHolder.Source = New Uri(newLocation.Text)
        End Sub

        Private Sub NewWindowHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim newWindowThread As New Thread(New ThreadStart(AddressOf ThreadStartingPoint))
            newWindowThread.SetApartmentState(ApartmentState.STA)
            newWindowThread.IsBackground = True
            newWindowThread.Start()
        End Sub

        Private Sub ThreadStartingPoint()
            Dim tempWindow As New Window1()
            tempWindow.Show()
            System.Windows.Threading.Dispatcher.Run()
        End Sub
    End Class
End Namespace

Bu kodun aşağıdaki iş parçacığı kesimleri, bu bağlamda bizimle ilgili en ilginç bir koddur:

private void NewWindowHandler(object sender, RoutedEventArgs e)
{
    Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
    newWindowThread.SetApartmentState(ApartmentState.STA);
    newWindowThread.IsBackground = true;
    newWindowThread.Start();
}
Private Sub NewWindowHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim newWindowThread As New Thread(New ThreadStart(AddressOf ThreadStartingPoint))
    newWindowThread.SetApartmentState(ApartmentState.STA)
    newWindowThread.IsBackground = True
    newWindowThread.Start()
End Sub

Bu yöntem, "yeni pencere" düğmesine tıklandığında çağrılır. Yeni bir iş parçacığı oluşturur ve zaman uyumsuz olarak başlatır.

private void ThreadStartingPoint()
{
    Window1 tempWindow = new Window1();
    tempWindow.Show();
    System.Windows.Threading.Dispatcher.Run();
}
Private Sub ThreadStartingPoint()
    Dim tempWindow As New Window1()
    tempWindow.Show()
    System.Windows.Threading.Dispatcher.Run()
End Sub

Bu yöntem, yeni iş parçacığının başlangıç noktasıdır. Bu iş parçacığının denetimi altında yeni bir pencere oluşturacağız. WPF Dispatcher yeni iş parçacığını yönetmek için otomatik olarak yeni bir oluşturur. Tüm bunları, pencereyi başlatmak için yapmanız gerekir Dispatcher .

Teknik ayrıntılar ve geçmiş noktaları

Iş parçacığı kullanarak bileşen yazma

Microsoft .NET Framework geliştirici kılavuzu, bir bileşenin istemcilerine zaman uyumsuz davranış sergileme şeklini gösteren bir model tanımlar (bkz. olay tabanlı zaman uyumsuz düzene genel bakış). Örneğin, FetchWeatherFromServer yöntemi yeniden kullanılabilir, grafik olmayan bir bileşene paketlemek istediğinizi varsayalım. standart Microsoft .NET Framework örüntüsünün ardından, bu, aşağıdakine benzer şekilde görünür.

public class WeatherComponent : Component
{
    //gets weather: Synchronous
    public string GetWeather()
    {
        string weather = "";

        //predict the weather

        return weather;
    }

    //get weather: Asynchronous
    public void GetWeatherAsync()
    {
        //get the weather
    }

    public event GetWeatherCompletedEventHandler GetWeatherCompleted;
}

public class GetWeatherCompletedEventArgs : AsyncCompletedEventArgs
{
    public GetWeatherCompletedEventArgs(Exception error, bool canceled,
        object userState, string weather)
        :
        base(error, canceled, userState)
    {
        _weather = weather;
    }

    public string Weather
    {
        get { return _weather; }
    }
    private string _weather;
}

public delegate void GetWeatherCompletedEventHandler(object sender,
    GetWeatherCompletedEventArgs e);
Public Class WeatherComponent
    Inherits Component
    'gets weather: Synchronous 
    Public Function GetWeather() As String
        Dim weather As String = ""

        'predict the weather

        Return weather
    End Function

    'get weather: Asynchronous 
    Public Sub GetWeatherAsync()
        'get the weather
    End Sub

    Public Event GetWeatherCompleted As GetWeatherCompletedEventHandler
End Class

Public Class GetWeatherCompletedEventArgs
    Inherits AsyncCompletedEventArgs
    Public Sub New(ByVal [error] As Exception, ByVal canceled As Boolean, ByVal userState As Object, ByVal weather As String)
        MyBase.New([error], canceled, userState)
        _weather = weather
    End Sub

    Public ReadOnly Property Weather() As String
        Get
            Return _weather
        End Get
    End Property
    Private _weather As String
End Class

Public Delegate Sub GetWeatherCompletedEventHandler(ByVal sender As Object, ByVal e As GetWeatherCompletedEventArgs)

GetWeatherAsync , bir arka plan iş parçacığı oluşturma gibi daha önce açıklanan tekniklerin birini kullanarak, çağrıyı zaman uyumsuz olarak işler, çağıran iş parçacığını engeller.

Bu düzenin en önemli bölümlerinden biri, Ile başlamak için MethodName yöntemini çağıran aynı iş parçacığında MethodName metodunu çağırıyor . bunu depolayarak wpf 'yi oldukça kolay bir şekilde yapabilirsiniz, CurrentDispatcher ancak grafik olmayan bileşen Windows Forms veya ASP.NET programlarında değil yalnızca wpf uygulamalarında kullanılabilir.

DispatcherSynchronizationContextSınıfı bu ihtiyacı ele alarak, diğer kullanıcı arabirimi çerçeveleriyle aynı zamanda daha basit bir sürümü olarak düşünün Dispatcher .

public class WeatherComponent2 : Component
{
    public string GetWeather()
    {
        return fetchWeatherFromServer();
    }

    private DispatcherSynchronizationContext requestingContext = null;

    public void GetWeatherAsync()
    {
        if (requestingContext != null)
            throw new InvalidOperationException("This component can only handle 1 async request at a time");

        requestingContext = (DispatcherSynchronizationContext)DispatcherSynchronizationContext.Current;

        NoArgDelegate fetcher = new NoArgDelegate(this.fetchWeatherFromServer);

        // Launch thread
        fetcher.BeginInvoke(null, null);
    }

    private void RaiseEvent(GetWeatherCompletedEventArgs e)
    {
        if (GetWeatherCompleted != null)
            GetWeatherCompleted(this, e);
    }

    private string fetchWeatherFromServer()
    {
        // do stuff
        string weather = "";

        GetWeatherCompletedEventArgs e =
            new GetWeatherCompletedEventArgs(null, false, null, weather);

        SendOrPostCallback callback = new SendOrPostCallback(DoEvent);
        requestingContext.Post(callback, e);
        requestingContext = null;

        return e.Weather;
    }

    private void DoEvent(object e)
    {
        //do stuff
    }

    public event GetWeatherCompletedEventHandler GetWeatherCompleted;
    public delegate string NoArgDelegate();
}
Public Class WeatherComponent2
    Inherits Component
    Public Function GetWeather() As String
        Return fetchWeatherFromServer()
    End Function

    Private requestingContext As DispatcherSynchronizationContext = Nothing

    Public Sub GetWeatherAsync()
        If requestingContext IsNot Nothing Then
            Throw New InvalidOperationException("This component can only handle 1 async request at a time")
        End If

        requestingContext = CType(DispatcherSynchronizationContext.Current, DispatcherSynchronizationContext)

        Dim fetcher As New NoArgDelegate(AddressOf Me.fetchWeatherFromServer)

        ' Launch thread
        fetcher.BeginInvoke(Nothing, Nothing)
    End Sub

    Private Sub [RaiseEvent](ByVal e As GetWeatherCompletedEventArgs)
        RaiseEvent GetWeatherCompleted(Me, e)
    End Sub

    Private Function fetchWeatherFromServer() As String
        ' do stuff
        Dim weather As String = ""

        Dim e As New GetWeatherCompletedEventArgs(Nothing, False, Nothing, weather)

        Dim callback As New SendOrPostCallback(AddressOf DoEvent)
        requestingContext.Post(callback, e)
        requestingContext = Nothing

        Return e.Weather
    End Function

    Private Sub DoEvent(ByVal e As Object)
        'do stuff
    End Sub

    Public Event GetWeatherCompleted As GetWeatherCompletedEventHandler
    Public Delegate Function NoArgDelegate() As String
End Class

İç içe pompalama

Bazen UI iş parçacığını tamamen kilitlemek uygun değildir. Sınıfın yöntemini ele alalım ShowMessageBox . Show Kullanıcı Tamam düğmesine tıklaana kadar döndürmez. Ancak, etkileşimli olması için ileti döngüsüne sahip olması gereken bir pencere oluşturur. Kullanıcının Tamam 'a tıklamasını beklerken, özgün uygulama penceresi Kullanıcı girişine yanıt vermez. Ancak, boyama iletilerini işlemeye devam eder. Özgün pencere, kapsandığınızda ve gösterildiğinde kendisini yeniden çizer.

Screenshot that shows a MessageBox with an OK button

Bazı iş parçacıklarının ileti kutusu penceresinde bir ücret olması gerekir. WPF yalnızca ileti kutusu penceresi için yeni bir iş parçacığı oluşturabilir, ancak bu iş parçacığı özgün penceredeki devre dışı bırakılmış öğeleri boyayamadı (karşılıklı dışlamanın önceki tartışmasını hatırlayın). Bunun yerine WPF, iç içe geçmiş bir ileti işleme sistemi kullanır. DispatcherSınıfı PushFrame , bir uygulamanın geçerli yürütme noktasını depolayan, sonra yeni bir ileti döngüsü Başlatan adlı özel bir yöntemi içerir. İç içe geçmiş ileti döngüsü tamamlandığında, yürütme özgün çağrıdan sonra devam eder PushFrame .

Bu durumda, PushFrame çağrısında program bağlamını korur MessageBox.Show ve arka plan penceresini yeniden boyamak ve ileti kutusu penceresine girişi işlemek için yeni bir ileti döngüsü başlatır. Kullanıcı Tamam ' ı tıklattığında ve açılır pencereyi temizlediğinde, iç içe geçmiş döngü çıkar ve çağrısından sonra devam eder Show .

Eski yönlendirilmiş olaylar

WPF 'deki yönlendirilmiş olay sistemi, olaylar oluşturulduğunda tüm ağaçları bilgilendirir.

<Canvas MouseLeftButtonDown="handler1" 
        Width="100"
        Height="100"
        >
  <Ellipse Width="50"
           Height="50"
           Fill="Blue" 
           Canvas.Left="30"
           Canvas.Top="50" 
           MouseLeftButtonDown="handler2"
           />
</Canvas>

Elips üzerinde sol fare düğmesine basıldığında handler2 yürütülür. handler2Tamamlandıktan sonra olay, Canvas işlemek için kullanılan nesnesine geçirilir handler1 . Bu yalnızca handler2 olay nesnesini açıkça işlenmiş olarak işaretlemediğinde gerçekleşir.

handler2Bu olayı işlemek çok uzun sürecek. handler2PushFrame, saat döndürmeyen bir iç içe ileti döngüsünü başlatmak için kullanabilir. handler2Bu ileti döngüsü tamamlandığında olayı işlenmiş olarak işaretlemeirse, etkinlik çok eski olsa bile ağaca geçirilir.

Yeniden giriş ve kilitleme

Ortak dil çalışma zamanının (CLR) kilitleme mekanizması tam olarak tek bir şekilde davranmayabilir; bir kilit istenirken bir iş parçacığının işlemi tamamen durdurmasından kaynaklanabilir. Gerçekte, iş parçacığı yüksek öncelikli iletileri almaya ve işlemeye devam eder. Bu, kilitlenmeleri önlemeye yardımcı olur ve arabirimlerin düşük ölçüde yanıt vermesini sağlar, ancak hafif hatalara yönelik olasılığı ortaya çıkarır. Bu sürenin büyük çoğunluğunda, bununla ilgili herhangi bir şey bilmeniz gerekmez, ancak nadir koşullarda (genellikle Win32 pencere iletilerini veya COM STA bileşenlerini içeren) Bu durum yararlı olabilir.

Çoğu arabirim, bir kullanıcı arabirimine birden fazla iş parçacığı tarafından hiçbir şekilde erişilmediği varsayımın altında çalıştığı için iş parçacığı güvenliği göz önünde bulundurularak oluşturulmazlar. Bu durumda, bu tek iş parçacığı beklenmedik zamanlarda çevresel değişiklikler yapabilir ve bu da DispatcherObject karşılıklı dışlama mekanizmasının çözülenmesinin beklenen etkileri olur. Aşağıdaki sözde kodu göz önünde bulundurun:

Diagram that shows threading reentrancy.

Çoğu zaman doğru bir şeydir, ancak bu, beklenmeyen bir şekilde yeniden giriş sorunları sorunlara neden olabileceği için WPF içinde zaman vardır. Bu nedenle, belirli anahtar süreleriyle WPF çağırır DisableProcessing , bu iş parçacığı için kilit yönergesini, her zamankı clr kilidi yerıne WPF yeniden giriş, serbest bir kilit kullanacak şekilde değiştirir.

Bu nedenle, CLR ekibi neden bu davranışı seçmişmidir? COM STA nesneleri ve sonlandırma iş parçacığı ile yapması gerekiyordu. Bir nesne atık olarak toplandığında, Finalize YÖNTEMI UI iş parçacığı değil, ayrılmış Sonlandırıcı iş parçacığında çalıştırılır. Kullanıcı arabirimi iş parçacığında oluşturulan bir COM STA nesnesi yalnızca kullanıcı arabirimi iş parçacığında atıyacağından, bu da sorunu çıkarmaktadır. CLR, bir öğesinin eşdeğerini yapar BeginInvoke (Bu örnekte Win32's kullanarak SendMessage ). Ancak UI iş parçacığı meşgulse, Sonlandırıcı iş parçacığı durduruldu ve COM STA nesnesi atılamaz ve bu da ciddi bir bellek sızıntısı oluşturur. Bu nedenle, CLR ekibi kilitleri yapmak için zor çağrıyı yaptığı gibi çalışır.

WPF görevi, Bellek sızıntısını yeniden bildirmeden beklenmedik bir şekilde yeniden giriş yapmaktan kaçınmaktır. bu nedenle her yerde yeniden giriş yapmayın.

Ayrıca bkz.