İş 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:

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.

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.

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.

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:

Ç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.