スレッド モデル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-based asynchronous programming. 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. 入力と応答の間に知覚可能な遅延があると、ユーザーに不満が生じる可能性があります。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. Windows では、UIUI コンポーネントの整合性を確保するためにこれを行っています。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. 構築時に、現在実行中のスレッドにリンクされた Dispatcher への参照が DispatcherObject に格納されます。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 の応答性に影響を与えることなく、このアイドル時間を建設的に使用できます。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. ユーザーが [Start](開始) ボタンをクリックすると、検索が開始されます。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. ボタンのクリック イベント ハンドラー内で検索全体を処理した場合、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. これは、ウィンドウの 1 つがスレッドを独占する可能性がある場合に特に当てはまります。This is especially true if there is any chance that one of the windows will monopolize the thread.

Windows エクスプローラーはこの方法で動作します。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. このように、いずれかのウィンドウで長時間実行またはブロックする操作によって、他のすべてのウィンドウがロックされることはありません。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. WPFWPF によって自動的に新しい Dispatcher が作成され、新しいスレッドが管理されます。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. これは、WPFWPF を使用して CurrentDispatcher を格納することで、とても簡単に実行できます。ただし、この非グラフィカル コンポーネントは、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. ユーザーが [OK] ボタンをクリックするまで 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.

[OK] ボタンのある 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. 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.

この場合、MessageBox.Show の呼び出し時にプログラム コンテキストが PushFrame に保持され、新しいメッセージ ループが開始され、背景ウィンドウが再描画され、メッセージ ボックス ウィンドウへの入力が処理されます。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. そのため、特定のキー時刻で、WPFWPF によって DisableProcessing が呼び出されます。これにより、通常の 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 では、(この場合は Win32 の SendMessage を使用して) BeginInvoke に相当する処理が実行されます。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