執行緒模型Threading Model

Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) 是設計來避免開發人員遇到執行緒的難題。is designed to save developers from the difficulties of threading. 因此, 大部分的WPFWPF開發人員都不需要撰寫使用多個執行緒的介面。As a result, the majority of WPFWPF developers won't have to write an interface that uses more than one thread. 由於多執行緒的程式非常複雜且很難偵錯,因此,若有單一執行緒解決方案,就應避免使用多執行緒程式。Because multithreaded programs are complex and difficult to debug, they should be avoided when single-threaded solutions exist.

不過, 無論架構有多完善, UIUI架構都無法為每種問題提供單一執行緒的解決方案。No matter how well architected, however, no UIUI framework will ever be able to provide a single-threaded solution for every sort of problem. WPFWPF已關閉, 但仍有多個執行緒改善使用者介面 (UI)user interface (UI)回應性或應用程式效能的情況。comes close, but there are still situations where multiple threads improve 使用者介面 (UI)user interface (UI) responsiveness or application performance. 討論一些背景資料之後,本文將說明這其中的一些情況,然後以一些較低層級詳細資訊的討論來做出結論。After discussing some background material, this paper explores some of these situations and then concludes with a discussion of some lower-level details.

注意

本主題討論如何使用非同步呼叫BeginInvoke的方法來進行執行緒。This topic discusses threading by using the BeginInvoke method for asynchronous calls. 您也可以藉由呼叫InvokeAsync方法 ( Action接受或Func<TResult>做為參數) 來進行非同步呼叫。You can also make asynchronous calls by calling the InvokeAsync method, which take an Action or Func<TResult> as a parameter. 方法會傳回DispatcherOperation<TResult>Task,其具有屬性。 DispatcherOperation InvokeAsyncThe InvokeAsync method returns a DispatcherOperation or DispatcherOperation<TResult>, which has a Task property. 您可以使用await關鍵字DispatcherOperation搭配或相關聯Task的。You can use the await keyword with either the DispatcherOperation or the associated Task. 如果Task您需要以同步DispatcherOperation方式等候或DispatcherOperation<TResult>所傳回的, 請呼叫DispatcherOperationWait擴充方法。If you need to wait synchronously for the Task that is returned by a DispatcherOperation or DispatcherOperation<TResult>, call the DispatcherOperationWait extension method. 呼叫Task.Wait會導致鎖死。Calling Task.Wait will result in a deadlock. 如需使用Task來執行非同步作業的詳細資訊, 請參閱工作平行處理原則。For more information about using a Task to perform asynchronous operations, see Task Parallelism. 方法也具有Action接受或Func<TResult>做為參數的多載。 InvokeThe Invoke method also has overloads that take an Action or Func<TResult> as a parameter. 您可以使用Invoke方法, Action透過傳入委派或Func<TResult>來進行同步呼叫。You can use the Invoke method to make synchronous calls by passing in a delegate, Action or Func<TResult>.

概觀和發送器Overview and the Dispatcher

應用程式通常會從兩個執行緒開始: 一個用於處理轉譯, 另一個UIUI用於管理。 WPFWPFTypically, WPFWPF applications start with two threads: one for handling rendering and another for managing the UIUI. 轉譯執行緒實際上會在背景中隱藏執行, 而UIUI執行緒會接收輸入、處理事件、繪製螢幕, 以及執行應用程式程式碼。The rendering thread effectively runs hidden in the background while the UIUI thread receives input, handles events, paints the screen, and runs application code. 大部分的應用程式都UIUI使用單一執行緒, 不過在某些情況下, 最好是使用多個執行緒。Most applications use a single UIUI thread, although in some situations it is best to use several. 我們稍後將使用範例來討論這一點。We’ll discuss this with an example later.

執行緒會將名為Dispatcher之物件內的工作專案排入佇列。 UIUIThe UIUI thread queues work items inside an object called a Dispatcher. Dispatcher 會依優先權選取工作項目,並逐一執行以完成每個工作項目。The Dispatcher selects work items on a priority basis and runs each one to completion. UIUI個執行緒都必須至少有Dispatcher一個, 而且Dispatcher每個執行緒只能在一個執行緒中執行工作專案。Every UIUI thread must have at least one Dispatcher, and each Dispatcher can execute work items in exactly one thread.

建立回應式且方便使用的應用程式的訣竅, 是將Dispatcher工作專案保持在最大的程度。The trick to building responsive, user-friendly applications is to maximize the Dispatcher throughput by keeping the work items small. 如此一來, Dispatcher在佇列中等待處理的專案永遠不會過時。This way items never get stale sitting in the Dispatcher queue waiting for processing. 輸入與回應之間任何可察覺到的延遲都能讓使用者感到挫折。Any perceivable delay between input and response can frustrate a user.

WPFWPF應用程式應該如何處理大型作業?How then are WPFWPF applications supposed to handle big operations? 如果您的程式碼牽涉到大型計算,或需要查詢某些遠端伺服器上的資料庫,又該怎麼做?What if your code involves a large calculation or needs to query a database on some remote server? 通常, 答案是在不同的執行緒中處理 big 作業, 讓UIUI執行緒可以自由地在Dispatcher佇列中的專案。Usually, the answer is to handle the big operation in a separate thread, leaving the UIUI thread free to tend to items in the Dispatcher queue. 當 big 作業完成時, 它可以將其結果回報給UIUI執行緒以供顯示。When the big operation is complete, it can report its result back to the UIUI thread for display.

在過去WindowsWindows , UIUI只允許由建立專案的執行緒存取元素。Historically, WindowsWindows allows UIUI elements to be accessed only by the thread that created them. 這表示負責某些長時間執行工作的背景執行緒無法在完成時更新文字方塊。This means that a background thread in charge of some long-running task cannot update a text box when it is finished. WindowsWindows這麼做是為了確保UIUI元件的完整性。does this to ensure the integrity of UIUI components. 如果背景執行緒已在繪製期間更新了清單方塊的內容,則該清單方塊看起來可能很奇怪。A list box could look strange if its contents were updated by a background thread during painting.

WPFWPF 有個內建的互斥機制,會強制執行這項協調。has a built-in mutual exclusion mechanism that enforces this coordination. 中的大部分WPFWPF類別都DispatcherObject是衍生自。Most classes in WPFWPF derive from DispatcherObject. 在結構上, DispatcherObject會儲存連結至目前Dispatcher執行中線程之的參考。At construction, a DispatcherObject stores a reference to the Dispatcher linked to the currently running thread. 實際上, DispatcherObject會與建立它的執行緒產生關聯。In effect, the DispatcherObject associates with the thread that creates it. 在程式執行期間, DispatcherObject可以呼叫其公用VerifyAccess方法。During program execution, a DispatcherObject can call its public VerifyAccess method. VerifyAccess檢查與Dispatcher目前線程相關聯的, 並將其與Dispatcher在結構中儲存的參考做比較。VerifyAccess examines the Dispatcher associated with the current thread and compares it to the Dispatcher reference stored during construction. 如果兩者不相符, VerifyAccess則會擲回例外狀況。If they don’t match, VerifyAccess throws an exception. VerifyAccess的目的是要在每個屬於的DispatcherObject方法開始時呼叫。VerifyAccess is intended to be called at the beginning of every method belonging to a DispatcherObject.

如果只有一個執行緒可以修改UIUI, 背景執行緒如何與使用者互動?If only one thread can modify the UIUI, how do background threads interact with the user? 背景執行緒可以要求UIUI執行緒代表其執行作業。A background thread can ask the UIUI thread to perform an operation on its behalf. 它的運作方式是使用Dispatcher UIUI執行緒的來註冊工作專案。It does this by registering a work item with the Dispatcher of the UIUI thread. 類別提供兩種註冊工作專案的方法: InvokeBeginInvokeDispatcherThe Dispatcher class provides two methods for registering work items: Invoke and BeginInvoke. 這兩種方法都會排程要執行的委派。Both methods schedule a delegate for execution. Invoke是同步呼叫, 也就是在UIUI執行緒實際完成執行委派之前, 它不會傳回。Invoke is a synchronous call – that is, it doesn’t return until the UIUI thread actually finishes executing the delegate. BeginInvoke是非同步, 而且會立即傳回。BeginInvoke is asynchronous and returns immediately.

會依優先順序排序其佇列中的元素。 DispatcherThe Dispatcher orders the elements in its queue by priority. 將元素新增至Dispatcher佇列時, 可以指定十個層級。There are ten levels that may be specified when adding an element to the Dispatcher queue. 這些優先順序會保留在DispatcherPriority列舉中。These priorities are maintained in the DispatcherPriority enumeration. 您可以在DispatcherPriority檔中找到有關層級Windows SDKWindows SDK的詳細資訊。Detailed information about DispatcherPriority levels can be found in the Windows SDKWindows SDK documentation.

作用中的執行緒:範例Threads in Action: The Samples

單一執行緒應用程式與長時間執行的計算A Single-Threaded Application with a Long-Running Calculation

大部分的圖形化使用者介面 (Gui) 都會在等候使用者互動時所產生的事件時, 花費大量時間閒置的部分。Most graphical user interfaces (GUIs) spend a large portion of their time idle while waiting for events that are generated in response to user interactions. 有了謹慎的程式設計, 就可以建設性使用此閒置時間, 而不UIUI會影響的回應能力。With careful programming this idle time can be used constructively, without affecting the responsiveness of the UIUI. 執行緒模型不允許輸入中斷UIUI執行緒中發生的作業。 WPFWPFThe WPFWPF threading model doesn’t allow input to interrupt an operation happening in the UIUI thread. 這表示您必須務必定期返回, 以處理Dispatcher擱置中的輸入事件, 然後才會過時。This means you must be sure to return to the Dispatcher periodically to process pending input events before they get stale.

參考下列範例:Consider the following example:

顯示質數執行緒的螢幕擷取畫面。

這個簡單的應用程式會從三開始向上計算,以搜尋質數。This simple application counts upwards from three, searching for prime numbers. 當使用者按一下 [開始] 按鈕時, 就會開始搜尋。When the user clicks the Start button, the search begins. 當程式找到質數時,會使用它的發現來更新使用者介面。When the program finds a prime, it updates the user interface with its discovery. 使用者隨時都能停止搜尋。At any point, the user can stop the search.

雖然夠簡單,但質數搜尋會永無止盡的繼續執行,其中會遇到一些難題。Although simple enough, the prime number search could go on forever, which presents some difficulties. 如果我們在按鈕的 click 事件處理常式內處理整個搜尋, 就不會讓UIUI執行緒有機會處理其他事件。If we handled the entire search inside of the click event handler of the button, we would never give the UIUI thread a chance to handle other events. UIUI將無法回應輸入或處理訊息。The UIUI would be unable to respond to input or process messages. 它永遠不會重新繪製,而且永遠不會回應按鈕 Click。It would never repaint and never respond to button clicks.

我們可以在個別執行緒中管理質數搜尋,但接著需要處理同步問題。We could conduct the prime number search in a separate thread, but then we would need to deal with synchronization issues. 使用單一執行緒的方法,我們可以直接更新標籤,以列出所找到的最大質數。With a single-threaded approach, we can directly update the label that lists the largest prime found.

如果我們將計算的工作分解成可管理的區塊, 我們可以定期返回Dispatcher和處理事件。If we break up the task of calculation into manageable chunks, we can periodically return to the Dispatcher and process events. 我們可以提供WPFWPF重新繪製和處理輸入的機會。We can give WPFWPF an opportunity to repaint and process input.

在計算與事件處理之間分割處理時間的最佳方式, 就是從Dispatcher管理計算。The best way to split processing time between calculation and event handling is to manage calculation from the Dispatcher. 藉由使用BeginInvoke方法, 我們可以在UIUI事件繪製來源的相同佇列中排程質數檢查。By using the BeginInvoke method, we can schedule prime number checks in the same queue that UIUI events are drawn from. 在範例中,我們一次只會排程單一質數檢查。In our example, we schedule only a single prime number check at a time. 質數檢查完成之後,我們會立即排程下次檢查。After the prime number check is complete, we schedule the next check immediately. 只有在處理暫UIUI止的事件之後, 才會進行這種檢查。This check proceeds only after pending UIUI events have been handled.

顯示發送器佇列的螢幕擷取畫面。

Microsoft WordMicrosoft Word 會使用這項機制來完成拼字檢查。accomplishes spell checking using this mechanism. 拼寫檢查會在背景中使用UIUI執行緒的閒置時間來完成。Spell checking is done in the background using the idle time of the UIUI thread. 讓我們看看程式碼。Let's take a look at the code.

下列範例顯示建立使用者介面的 XAML。The following example shows the XAML that creates the user interface.

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

下列範例顯示程式碼後置。The following example shows the code-behind.

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

下列範例會顯示的事件處理常式ButtonThe following example shows the event handler for the Button.

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

除了更新上Button的文字, 此處理程式還負責將委派新增Dispatcher至佇列, 以排程第一個質數檢查。Besides updating the text on the Button, this handler is responsible for scheduling the first prime number check by adding a delegate to the Dispatcher queue. 當這個事件處理常式完成其工作之後, Dispatcher將會選取這個委派來執行。Sometime after this event handler has completed its work, the Dispatcher will select this delegate for execution.

如先前所述, BeginInvoke Dispatcher是用來排程執行委派的成員。As we mentioned earlier, BeginInvoke is the Dispatcher member used to schedule a delegate for execution. 在此情況下, 我們會SystemIdle選擇優先級。In this case, we choose the SystemIdle priority. 只有Dispatcher在沒有要處理的重要事件時, 才會執行這個委派。The Dispatcher will execute this delegate only when there are no important events to process. UIUI 回應性比數字檢查更重要。responsiveness is more important than number checking. 我們也會傳遞新的委派來代表數字檢查常式。We also pass a new delegate representing the number-checking routine.

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

這個方法會檢查下一個奇數是否為質數。This method checks if the next odd number is prime. 如果是質數, 方法會直接更新bigPrime TextBlock以反映其探索。If it is prime, the method directly updates the bigPrimeTextBlock to reflect its discovery. 由於計算會發生在用來建立元件的相同執行緒中,因此我們可以執行這項操作。We can do this because the calculation is occurring in the same thread that was used to create the component. 我們已選擇使用個別的執行緒來進行計算, 我們必須使用更複雜的同步處理機制, 並在UIUI執行緒中執行更新。Had we chosen to use a separate thread for the calculation, we would have to use a more complicated synchronization mechanism and execute the update in the UIUI thread. 接下來我們將示範這種情況。We’ll demonstrate this situation next.

如需此範例的完整原始程式碼, 請參閱具有長時間執行計算範例的單一執行緒應用程式For the complete source code for this sample, see the Single-Threaded Application with Long-Running Calculation Sample

利用背景執行緒處理封鎖作業Handling a Blocking Operation with a Background Thread

處理圖形應用程式中的封鎖作業可能很困難。Handling blocking operations in a graphical application can be difficult. 我們不想從事件處理常式中呼叫封鎖方法,因為應用程式將呈現凍結狀態。We don’t want to call blocking methods from event handlers because the application will appear to freeze up. 我們可以使用個別的執行緒來處理這些作業, 但當我們完成時, 必須與UIUI執行緒同步處理, 因為我們無法直接從背景工作執行緒修改 GUI。We can use a separate thread to handle these operations, but when we’re done, we have to synchronize with the UIUI thread because we can’t directly modify the GUI from our worker thread. 我們可以使用InvokeBeginInvoke ,將UIUI委派插入執行緒的中。 DispatcherWe can use Invoke or BeginInvoke to insert delegates into the Dispatcher of the UIUI thread. 最後, 這些委派將會以修改UIUI元素的許可權執行。Eventually, these delegates will be executed with permission to modify UIUI elements.

在此範例中,我們模仿遠端程序呼叫來擷取氣象預報。In this example, we mimic a remote procedure call that retrieves a weather forecast. 我們會使用個別的背景工作執行緒來執行此呼叫, 並在完成時, 將Dispatcher update 方法UIUI排程線上程的中。We use a separate worker thread to execute this call, and we schedule an update method in the Dispatcher of the UIUI thread when we’re finished.

顯示天氣 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
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

以下是一些要注意的詳細資料。The following are some of the details to be noted.

  • 建立按鈕處理常式Creating the Button Handler

    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
    

按一下按鈕時,我們會顯示時鐘圖案並開始以動畫顯示它。When the button is clicked, we display the clock drawing and start animating it. 我們停用按鈕。We disable the button. 我們會在新的執行緒中叫用Dispatcher 方法,然後傳回,讓能夠在我們等候收集氣象預報時處理事件。FetchWeatherFromServerWe invoke the FetchWeatherFromServer method in a new thread, and then we return, allowing the Dispatcher to process events while we wait to collect the weather forecast.

  • 擷取氣象Fetching the Weather

    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
    

為了簡單起見,我們並未在此範例中實際使用任何網路程式碼。To keep things simple, we don’t actually have any networking code in this example. 相反地,我們讓新的執行緒進入睡眠狀態 4 秒,藉以模擬網路存取延遲。Instead, we simulate the delay of network access by putting our new thread to sleep for four seconds. 此時, 原始UIUI執行緒仍在執行中並回應事件。In this time, the original UIUI thread is still running and responding to events. 為了顯示此情況,我們讓動畫持續執行,並讓最小化及最大化按鈕也能繼續運作。To show this, we’ve left an animation running, and the minimize and maximize buttons also continue to work.

當延遲完成, 而且我們隨機選取了氣象預報之後, 就可以向後回報至UIUI執行緒。When the delay is finished, and we’ve randomly selected our weather forecast, it’s time to report back to the UIUI thread. 我們的作法是UpdateUserInterface UIUI線上程中使用該執行緒的Dispatcher來排程的呼叫。We do this by scheduling a call to UpdateUserInterface in the UIUI thread using that thread’s Dispatcher. 我們將描述氣象的字串傳遞到這個排程的方法呼叫。We pass a string describing the weather to this scheduled method call.

  • 正在更新UIUIUpdating the UIUI

    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
    

Dispatcher 執行緒UIUI中的具有時間時, 它會UpdateUserInterface執行排程的呼叫。When the Dispatcher in the UIUI thread has time, it executes the scheduled call to UpdateUserInterface. 這個方法會停止時鐘動畫,並選擇影像來說明氣象。This method stops the clock animation and chooses an image to describe the weather. 它會顯示此影像,並還原 [Fetch Forecast (擷取預報)] 按鈕。It displays this image and restores the "fetch forecast" button.

多個視窗,多個執行緒Multiple Windows, Multiple Threads

某些WPFWPF應用程式需要多個最上層視窗。Some WPFWPF applications require multiple top-level windows. 一個執行緒/Dispatcher組合可以完全接受, 以管理多個視窗, 但有時候有數個執行緒會有更好的工作。It is perfectly acceptable for one Thread/Dispatcher combination to manage multiple windows, but sometimes several threads do a better job. 如果有可能發生某一個視窗將獨佔執行緒的情況,這特別適用。This is especially true if there is any chance that one of the windows will monopolize the thread.

WindowsWindows 檔案總管會以這種方式運作。Explorer works in this fashion. 每個新的檔案總管視窗都屬於原始的程序,但會在獨立執行緒的控制下建立它。Each new Explorer window belongs to the original process, but it is created under the control of an independent thread.

藉由使用WPFWPF Frame控制項, 我們可以顯示網頁。By using a WPFWPFFrame control, we can display Web pages. 我們可以輕鬆地建立簡單的 Internet Explorer 替代方案。We can easily create a simple Internet Explorer substitute. 我們從一個重要功能開始︰開啟新檔案總管視窗的能力。We start with an important feature: the ability to open a new explorer window. 當使用者按一下 [New Window (新視窗)] 按鈕時,我們會在另一個執行緒中啟動視窗複本。When the user clicks the "new window" button, we launch a copy of our window in a separate thread. 如此一來,在其中一個視窗中長時間執行或封鎖的作業就不會鎖定所有其他視窗。This way, long-running or blocking operations in one of the windows won’t lock all the other windows.

事實上,網頁瀏覽器模型有它自己的複雜執行緒模型。In reality, the Web browser model has its own complicated threading model. 我們選擇它是因為大多數讀者應該都已熟悉它。We’ve chosen it because it should be familiar to most readers.

下列範例顯示此程式碼。The following example shows the code.

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

此程式碼的下列執行緒區段是我們在此內容中最感興趣的部分:The following threading segments of this code are the most interesting to us in this context:

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

按下 [New Window (新視窗)] 按鈕時會呼叫此方法。This method is called when the "new window" button is clicked. 它會建立新的執行緒,並以非同步方式啟動它。It creates a new thread and starts it asynchronously.

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

這個方法是新執行緒的起點。This method is the starting point for the new thread. 我們在此執行緒的控制下建立新視窗。We create a new window under the control of this thread. WPFWPF會自動建立新Dispatcher的來管理新的執行緒。automatically creates a new Dispatcher to manage the new thread. 為了讓視窗運作, 我們只需要啟動DispatcherAll we have to do to make the window functional is to start the Dispatcher.

技術詳細資料與困難點Technical Details and Stumbling Points

使用執行緒撰寫元件Writing Components Using Threading

《 Microsoft .NET Framework 開發人員指南》會說明元件如何將非同步行為公開給其用戶端的模式 (請參閱事件架構非同步模式總覽)。The Microsoft .NET Framework Developer's Guide describes a pattern for how a component can expose asynchronous behavior to its clients (see Event-based Asynchronous Pattern Overview). 比方說, 假設我們想要將FetchWeatherFromServer方法封裝成可重複使用的 nongraphical 元件。For instance, suppose we wanted to package the FetchWeatherFromServer method into a reusable, nongraphical component. 遵循標準的 Microsoft .NET Framework 模式, 這看起來會像下面這樣。Following the standard Microsoft .NET Framework pattern, this would look something like the following.

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 會使用上述其中一種技術 (例如建立背景執行緒),以非同步方式執行工作,而不需封鎖呼叫執行緒。GetWeatherAsync would use one of the techniques described earlier, such as creating a background thread, to do the work asynchronously, not blocking the calling thread.

此模式最重要的部分之一, 就是在呼叫 Completed方法 Async方法的相同執行緒上呼叫方法方法, 以開始使用。One of the most important parts of this pattern is calling the MethodNameCompleted method on the same thread that called the MethodNameAsync method to begin with. 您可以輕鬆地透過WPFWPF儲存CurrentDispatcher來執行這項操作, 但 nongraphical 元件只能Windows FormsWindows Forms在應用程式中使用WPFWPF , 而不能用於或 ASP.NET 程式中。You could do this using WPFWPF fairly easily, by storing CurrentDispatcher—but then the nongraphical component could only be used in WPFWPF applications, not in Windows FormsWindows Forms or ASP.NET programs.

此類別可滿足這項需求, 也就是將它視為適用于其他UIUI架構的簡化版本。 Dispatcher DispatcherSynchronizationContextThe DispatcherSynchronizationContext class addresses this need—think of it as a simplified version of Dispatcher that works with other UIUI frameworks as well.

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

巢狀提取Nested Pumping

有時候, 完全鎖定UIUI執行緒並不可行。Sometimes it is not feasible to completely lock up the UIUI thread. 讓我們來看一下MessageBox類別的方法。ShowLet’s consider the Show method of the MessageBox class. Show在使用者按一下 [確定] 按鈕之前不會傳回。Show doesn’t return until the user clicks the OK button. 不過,它會建立必須有訊息迴圈才能互動的視窗。It does, however, create a window that must have a message loop in order to be interactive. 雖然我們正在等待使用者按下 [OK (確定)],但原始的應用程式視窗並不會回應使用者輸入。While we are waiting for the user to click OK, the original application window does not respond to user input. 不過,它會繼續處理繪製訊息。It does, however, continue to process paint messages. 原始視窗會在涵蓋並顯示時自行重新繪製。The original window redraws itself when covered and revealed.

顯示含有 [確定] 按鈕之 MessageBox 的螢幕擷取畫面

有些執行緒必須負責訊息方塊視窗。Some thread must be in charge of the message box window. WPFWPF 只會針對訊息方塊視窗建立新的執行緒,但這個執行緒無法在原始視窗中繪製已停用的元素 (請記住先前討論過的互斥)。could create a new thread just for the message box window, but this thread would be unable to paint the disabled elements in the original window (remember the earlier discussion of mutual exclusion). 相反地WPFWPF , 會使用嵌套的訊息處理系統。Instead, WPFWPF uses a nested message processing system. 類別包含一個稱為PushFrame的特殊方法, 它會儲存應用程式的目前執行點, 然後開始新的訊息迴圈。 DispatcherThe Dispatcher class includes a special method called PushFrame, which stores an application’s current execution point then begins a new message loop. 當 nested 訊息迴圈完成時, 會在原始PushFrame呼叫之後繼續執行。When the nested message loop finishes, execution resumes after the original PushFrame call.

在此情況下PushFrame , 會在MessageBox呼叫Show時維護程式內容, 並啟動新的訊息迴圈來重新繪製背景視窗, 並處理訊息方塊視窗的輸入。In this case, PushFrame maintains the program context at the call to MessageBox.Show, and it starts a new message loop to repaint the background window and handle input to the message box window. 當使用者按一下 [確定] 並清除快顯視窗時, 嵌套的迴圈會在呼叫Show之後結束, 並繼續進行控制。When the user clicks OK and clears the pop-up window, the nested loop exits and control resumes after the call to Show.

過時的路由事件Stale Routed Events

WPFWPF的路由事件系統會在引發事件時通知整個樹狀結構。The routed event system in WPFWPF notifies entire trees when events are raised.

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

在橢圓形上按下滑鼠左鍵時, handler2會執行。When the left mouse button is pressed over the ellipse, handler2 is executed. 完成handler2之後, 事件會傳遞Canvas至物件, 以用handler1來處理它。After handler2 finishes, the event is passed along to the Canvas object, which uses handler1 to process it. 只有在未明確handler2地將事件物件標示為已處理時, 才會發生這種情況。This happens only if handler2 does not explicitly mark the event object as handled.

這可能handler2會花很多時間來處理此事件。It’s possible that handler2 will take a great deal of time processing this event. handler2可能會PushFrame使用來開始不會傳回小時的嵌套訊息迴圈。handler2 might use PushFrame to begin a nested message loop that doesn’t return for hours. 如果handler2未在此訊息迴圈完成時將事件標示為已處理, 則會將事件傳遞到樹狀結構中, 即使它非常舊也一樣。If handler2 does not mark the event as handled when this message loop is complete, the event is passed up the tree even though it is very old.

重新進入和鎖定Reentrancy and Locking

通用語言執行時間 (CLR) 的鎖定機制, 其行為不會與想像的完全相同;要求鎖定時, 可能會預期執行緒完全停止作業。The locking mechanism of the common language runtime (CLR) doesn’t behave exactly as one might imagine; one might expect a thread to cease operation completely when requesting a lock. 實際上,執行緒會繼續接收和處理高優先順序的訊息。In actuality, the thread continues to receive and process high-priority messages. 這有助於防止發生鎖死,並讓介面進行最低限度的回應,但它也會造成發生輕微 Bug 的可能性。This helps prevent deadlocks and make interfaces minimally responsive, but it introduces the possibility for subtle bugs. 在大部分的情況下, 您不需要知道這方面的任何內容, 但在罕見的情況下Win32Win32 (通常涉及視窗訊息或 COM STA 元件), 這很值得了解。The vast majority of the time you don’t need to know anything about this, but under rare circumstances (usually involving Win32Win32 window messages or COM STA components) this can be worth knowing.

大部分的介面不是以執行緒安全的方式建立的, 因為開發人員的UIUI工作是不是由一個以上的執行緒所存取。Most interfaces are not built with thread safety in mind because developers work under the assumption that a UIUI is never accessed by more than one thread. 在此情況下, 該單一執行緒可能會在非預期的時間進行環境變更, 而導致DispatcherObject相互排除機制應解決的錯誤效果。In this case, that single thread may make environmental changes at unexpected times, causing those ill effects that the DispatcherObject mutual exclusion mechanism is supposed to solve. 請考慮下列虛擬程式碼:Consider the following pseudocode:

顯示執行緒重新進入的圖表。Diagram that shows threading reentrancy.

大部分時間都是正確的, 但WPFWPF有時候這類非預期的重新進入可能會造成問題。Most of the time that’s the right thing, but there are times in WPFWPF where such unexpected reentrancy can really cause problems. 因此, 在某些索引鍵時間WPFWPF , DisableProcessing WPFWPF會呼叫, 這會變更該執行緒的鎖定指令, 以使用無重新進入的鎖定, 而不是一般的 CLR 鎖定。So, at certain key times, WPFWPF calls DisableProcessing, which changes the lock instruction for that thread to use the WPFWPF reentrancy-free lock, instead of the usual CLR lock.

那麼, 為什麼 CLR 小組選擇此行為呢?So why did the CLR team choose this behavior? 它必須使用 STA COM 物件和完成項執行緒來執行。It had to do with COM STA objects and the finalization thread. 當物件進行垃圾收集時, 其Finalize方法會在專用完成項執行緒上執行, 而UIUI不是線上程上執行。When an object is garbage collected, its Finalize method is run on the dedicated finalizer thread, not the UIUI thread. 其中的問題在於, 因為在UIUI執行緒上建立的 COM STA 物件只能UIUI線上程上處置。And therein lies the problem, because a COM STA object that was created on the UIUI thread can only be disposed on the UIUI thread. CLR 會執行對等BeginInvoke的 (在此案例中使用 win32 SendMessage)。The CLR does the equivalent of a BeginInvoke (in this case using Win32’s SendMessage). 但是, 如果UIUI執行緒忙碌中, 完成項執行緒就會停止, 而且無法處置 COM STA 物件, 這會造成嚴重的記憶體流失。But if the UIUI thread is busy, the finalizer thread is stalled and the COM STA object can’t be disposed, which creates a serious memory leak. 因此, CLR 小組進行了很難的呼叫, 讓鎖定的執行方式。So the CLR team made the tough call to make locks work the way they do.

的工作是避免非預期的重新進入, 而不重新介紹基於記憶體流失, 這就是為什麼我們不會封鎖任何地方的重新進入。 WPFWPFThe task for WPFWPF is to avoid unexpected reentrancy without reintroducing the memory leak, which is why we don’t block reentrancy everywhere.

另請參閱See also