スレッド モデル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. また、パラメーターとして Action または Func<TResult> を受け取る InvokeAsync メソッドを呼び出すことによって、非同期呼び出しを行うこともできます。You can also make asynchronous calls by calling the InvokeAsync method, which take an Action or Func<TResult> as a parameter. InvokeAsync メソッドは、Task プロパティを持つ DispatcherOperation または DispatcherOperation<TResult>を返します。The 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. Invoke メソッドには、パラメーターとして Action または Func<TResult> を受け取るオーバーロードもあります。The 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

通常、WPFWPF アプリケーションは2つのスレッドで開始されます。1つはレンダリングを処理し、もう1つは UIUIを管理します。Typically, 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. ほとんどのアプリケーションは1つの 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.

UIUI スレッドは、Dispatcherと呼ばれるオブジェクト内の作業項目をキューに配置します。The 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 スレッドには少なくとも1つの Dispatcherが必要であり、各 Dispatcher は1つのスレッドで作業項目を実行できます。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. 入力と応答の間の perceivable 遅延によって、ユーザーの不満が生じる可能性があります。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? 通常、この応答は、大きな操作を別のスレッドで処理することで、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. 大規模な操作が完了すると、結果を表示するために UIUI のスレッドに報告できます。When the big operation is complete, it can report its result back to the UIUI thread for display.

従来、Windows では、UIUI 要素には、それを作成したスレッドだけがアクセスできるようになっています。Historically, Windows 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. これは、UIUI コンポーネントの整合性を確保するために Windows によって行われます。Windows 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を変更できるのが1つのスレッドだけの場合、バックグラウンドスレッドがユーザーとどのように対話しますか。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. これを行うには、作業項目を UIUI スレッドの Dispatcher に登録します。It does this by registering a work item with the Dispatcher of the UIUI thread. Dispatcher クラスには、作業項目を登録するための2つの方法 (InvokeBeginInvoke) が用意されています。The 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.

Dispatcher は、優先順位によってキュー内の要素を並べ替えます。The Dispatcher orders the elements in its queue by priority. Dispatcher キューに要素を追加するときに指定できるレベルは10個あります。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 SDK のドキュメントを参照してください。Detailed information about DispatcherPriority levels can be found in the Windows 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の応答性に影響を与えることなく、このアイドル時間を constructively に使用できます。With careful programming this idle time can be used constructively, without affecting the responsiveness of the UIUI. WPFWPF のスレッドモデルでは、UIUI のスレッドで発生する操作を中断するための入力を許可していません。The 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:

素数のスレッド処理を示すスクリーンショット。

この単純なアプリケーションは、3から昇順にカウントし、素数を検索します。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. これは再描画せず、ボタンのクリックに応答することもありません。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. この例では、一度に1つの素数チェックのみをスケジュールします。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 Word は、このメカニズムを使用してスペルチェックを行います。Microsoft 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.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

次の例は、Buttonのイベントハンドラーを示しています。The 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. 素数の場合、メソッドは、検出を反映するために bigPrimeTextBlock を直接更新します。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. これらの操作を処理するために別のスレッドを使用できますが、完了したら、ワーカースレッドから GUI を直接変更することはできないため、UIUI スレッドと同期する必要があります。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. Invoke または BeginInvoke を使用して、UIUI スレッドの Dispatcher にデリゲートを挿入できます。We 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. この呼び出しを実行するために別のワーカースレッドを使用し、終了時に UIUI スレッドの Dispatcher で更新メソッドをスケジュールします。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.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. 新しいスレッドで FetchWeatherFromServer メソッドを呼び出した後、を返します。これにより、天気予報の収集を待機している間に Dispatcher がイベントを処理できるようになります。We 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. これを行うには、そのスレッドの Dispatcherを使用して、UIUI スレッドで UpdateUserInterface の呼び出しをスケジュールします。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.

  • UIUI の更新Updating 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
    

UIUI スレッド内の Dispatcher に時間がある場合、スケジュールされている 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. このイメージが表示され、"フェッチ予測" ボタンが復元されます。It displays this image and restores the "fetch forecast" button.

複数のウィンドウ、複数のスレッドMultiple Windows, Multiple Threads

一部の WPFWPF アプリケーションでは、複数のトップレベルウィンドウが必要です。Some WPFWPF applications require multiple top-level windows. 1つのスレッドと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.

Windows Explorer はこの方法で動作します。Windows 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.

WPFWPFFrame コントロールを使用して、Web ページを表示できます。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. ユーザーが [新しいウィンドウ] ボタンをクリックすると、ウィンドウのコピーが別のスレッドで起動されます。When the user clicks the "new window" button, we launch a copy of our window in a separate thread. これにより、windows の1つで実行時間の長い操作またはブロック操作によって、他のウィンドウがすべてロックされることはありません。This way, long-running or blocking operations in one of the windows won’t lock all the other windows.

実際には、Web ブラウザーモデルには独自の複雑なスレッドモデルがあります。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.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

このメソッドは、[新しいウィンドウ] ボタンがクリックされたときに呼び出されます。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. 新しいスレッドを管理する新しい Dispatcher は、WPFWPF によって自動的に作成されます。WPFWPF automatically creates a new Dispatcher to manage the new thread. ウィンドウを機能させるために必要な作業は、Dispatcherを開始することだけです。All 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 メソッドを再利用可能な非グラフィカルコンポーネントにパッケージ化するとします。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.

このパターンの最も重要な部分の1つは、 methodnameAsync メソッドを呼び出したのと同じスレッドでmethodnameCompleted メソッドを呼び出すことです。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. これは、CurrentDispatcherを格納することによって WPFWPF 非常に簡単に実行できますが、グラフィカルでないコンポーネントは、Windows フォームや ASP.NET プログラムではなく WPFWPF アプリケーションでのみ使用できます。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 Forms or ASP.NET programs.

DispatcherSynchronizationContext クラスはこのニーズに対応しており、他の UIUI フレームワークとも連携する Dispatcher の簡略化されたバージョンと考えることができます。The 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 クラスの Show メソッドについて考えてみましょう。Let’s consider the Show method of the MessageBox class. Show は、ユーザーが [OK] ボタンをクリックするまで戻りません。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 と [OK] ボタンを示すスクリーンショット

スレッドによっては、メッセージボックスウィンドウが使用されている必要があります。Some thread must be in charge of the message box window. メッセージボックスウィンドウだけに新しいスレッドを作成 WPFWPF ことがありますが、このスレッドでは、元のウィンドウで無効になっている要素を描画することはできません (前述の相互排他の説明を思い出してください)。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. Dispatcher クラスには、PushFrameと呼ばれる特殊なメソッドが含まれています。このメソッドは、アプリケーションの現在の実行ポイントを格納し、新しいメッセージループを開始します。The Dispatcher class includes a special method called PushFrame, which stores an application’s current execution point then begins a new message loop. 入れ子になったメッセージループが終了すると、元の PushFrame 呼び出しの後に実行が再開されます。When the nested message loop finishes, execution resumes after the original PushFrame call.

この場合、PushFrameMessageBox.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. ユーザーが [OK] をクリックしてポップアップウィンドウをクリアすると、入れ子になったループが終了し、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. これにより、デッドロックを防止し、インターフェイスの応答性を最小限にすることができますが、軽度のバグが生じる可能性があります。This helps prevent deadlocks and make interfaces minimally responsive, but it introduces the possibility for subtle bugs. ほとんどの場合、この点について知っておく必要はありませんが、まれな状況 (通常は Win32 ウィンドウメッセージまたは COM STA コンポーネントが関係します) では、このことを理解しておく価値があります。The vast majority of the time you don’t need to know anything about this, but under rare circumstances (usually involving Win32 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. この場合、1つのスレッドによって環境の変更が予期しない時間に行われる可能性があるため、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. そのため、特定のキー時に WPFWPFDisableProcessingを呼び出します。これにより、通常の CLR ロックではなく、再入可能なロック WPFWPF を使用するように、そのスレッドのロック命令が変更されます。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? これは、COM STA オブジェクトと終了スレッドで実行する必要がありました。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's 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.

WPFWPF のタスクでは、メモリリークを再導入ことなく予期しない再入を避けることができます。これは、どこでも再入をブロックしないためです。The task for WPFWPF is to avoid unexpected reentrancy without reintroducing the memory leak, which is why we don’t block reentrancy everywhere.

参照See also