WPF Fundamentals - Threading Model WPF




[ Overview and the Disptacher & Thread ]
http://msdn2.microsoft.com/en-us/library/ms741870.aspx
WPF 애플리케이션은 Rendering제어쓰레드와 UI관리쓰레드 두 쓰레로 시작한다.
Rendering Thread는 UI쓰레드가 입력,이벤트처리,화면그리기,코드수행 등을 하는동안 백그라운드로 돌면서 효과적으로 수행된다.
대부분UI쓰레드 하나면되지만 여러개가 필요한 경우가 있다.

   
 

UI Thread가 Dispatcher에서 작업아이템들을 우선순위별(10레벨)로 빼내면서 Dispatcher에게 해당작업 실행을 시키게 한다.
UI Thread는 반드시 하나 이상의 Dispatcher를 가져야하고  각각의 Dispatcher는 반드시 하나의 쓰레드에서 실행될수 있다.

꼭필요한 Work Item들만 넣어 Dispatcher의 처리량을 높이므로써 최적의 응답성을 나타나게 할 수 있다.

시간을 많이 필요한 작업의 경우는 UI쓰레드 독립적으로 도는 별도의 쓰레드를 생성하여 처리 완료 시 UI쓰레드에게 콜백.

Windows에서 UI Element는 자신을 생성한 쓰레드만 접근을 허용한다. (UI 무결성을 보장하기위해)
(예로 리스트박스를 렌드링 하는 중에 백그라운드 쓰레드가 데이타를 업데이트 시키면 이상한 출력현상을 보임)

WPF는 다음의 방법으로 상호배제 메커니즘(여기서는 생성한 쓰레가 UI에 접근) 가능하게한다. 
1. 모든 클래스는 DependancyObject로부터 상속된다.

   생성 시 현재 실행중인 쓰레드에 연결된 Dispatcher의 포인터를 저장.
결과 DependancyObject와 Thread가 연결됨
2. 프로그램 실행중에 DependancyObject는 자신의 VerifyAccess 공개메소드를 호출

   VerifyAccess는 현재실행중인 쓰레드와 관련된 Dispatch를 조사하여 생성시 저장한 Dispatcher포인터와 비교 일치하지 않으면 익셉션을 발생시킨다.

   VerifyAccess는 DependancyObject가 소유한 메소드가 호출되기전에 호출하도록 의도 되어졌다.

3. 만약  background thread가는 자신의 해야할 작업이 UI와 관련된 작업인 경우 그 작업을 Dispatcher의 Invoke,BeginInvoke이용 
   하여Dispatcher에 등록, 두 메서드는 실행에대한 하나의 대리자로써 스케줄링 된다.?

   Invoke는 동기콜(실행종료 시 리턴),BeginInvoke는 비동기콜(바로리턴)

   
 

예제 )최대 소수를 찾는 프로그램 (UI쓰레가 idle상태일 때 백그라운드로 소수를 찾는다(MS Word의 splling shecker도 이런법이용)
아래 그림처럼 Dispatcher Queue를 이용하여 UI쓰레드의 블록킹을 없앤다.( 비동기 처리의 아주 일반적인 방법이다)




XAML- checkNextNumber라는 콜백을 계속해서 Dispatcher에 우선순위를 지정하여 넣는다. 생각보다 단순한 방식으로 백그라운드 쓰레드를 처리한다.
CheckNextNumber는 백그라운드쓰레드(비동기Delegate함수)처럼 작동할지라도 Window1의 멤버함수 여서 UI Element접근에 문제가 없다.(결국 해당UI쓰레드의 DispatcherQueue내의 작업을 우선순위에따라 처리해가는것임)

SystemIdle일때 처리하고 또 큐에 넣어두고를 반복 
<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> 

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();
        }

        public 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 ture.
                    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, // 다음호출시에는 UI쓰레드가 Idle할떄
                       new NextPrimeDelegate(this.CheckNextNumber));
            }
        }
       
        private bool NotAPrime = false;
    }
}

예제)Handling a Blocking Operation with a Background Thread
 Weather Service Simulation Via Dispatcher Sample

Background Thread는 실재적으로 특정 UI컨트롤이 Dispatcher를 이용해서 생성하는 Delegate라는것에 주의하자
Dispatcher가 자신의 내부 큐에 Job으로 Delegate포인터를 우선순위에 따라 넣어두기만 하면 해당 UI가 순서대로 처리해간다.

각컨트롤별로 Dispatcher를 소유하는것인가? 아니면 프로세스내의 모든 컨트롤들은 동일 Dispather를 공유하는가? 후자일듯.
아닌거 같기도 하고? Frame이나 NavigationWindow는 Url의 Data를 자체적인 쓰레딩으로 출력을 할텐데.. 이들을 포함하고 있는 윈도우의 쓰레드와 Dispatcher을 공유할까? 나중에 확인해봐야 할 사항이네요

- XAML , Geometry 부분은 나중에 상세히 또 봐야 될 부분. 비하인드 코드 위주로 보면 이해됨

<Window x:Class="SDKSamples.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ThreadingSampleWeatherForecast"
    Height="300"
    Width="175"
    Loaded="Window_Loaded">
  <Window.Resources>

    <Storyboard x:Key="ShowClockFaceStoryboard">
      <DoubleAnimation  Storyboard.TargetName="ClockImage" Storyboard.TargetProperty="Opacity"
        From="0" To="1" Duration="0:0:0.1" />
      <DoubleAnimation Storyboard.TargetName="ClockFaceScaleTransform" Storyboard.TargetProperty="ScaleX"
        From="0" To="1" Duration="0:0:0.5" />
      <DoubleAnimation Storyboard.TargetName="ClockFaceScaleTransform" Storyboard.TargetProperty="ScaleY"
        From="0" To="1" Duration="0:0:0.5" />
      <DoubleAnimation  Storyboard.TargetName="ClockHandRotateTransform" Storyboard.TargetProperty="Angle"
        From="0" To="360" Duration="0:0:2"  RepeatBehavior="Forever" />
    </Storyboard>

    <Storyboard x:Key="HideClockFaceStoryboard" Completed="HideClockFaceStoryboard_Completed">
      <DoubleAnimation  Storyboard.TargetName="ClockImage" Storyboard.TargetProperty="Opacity"
        From="1" To="0" Duration="0:0:0.25" />
      <DoubleAnimation Storyboard.TargetName="ClockFaceScaleTransform" Storyboard.TargetProperty="ScaleX"
        From="1" To="0" Duration="0:0:0.25" />
      <DoubleAnimation Storyboard.TargetName="ClockFaceScaleTransform" Storyboard.TargetProperty="ScaleY"
        From="1" To="0" Duration="0:0:0.25" />
    </Storyboard>

    <Storyboard x:Key="ShowWeatherImageStoryboard">
      <DoubleAnimation Storyboard.TargetName="weatherIndicatorImage" Storyboard.TargetProperty="Opacity"
        To="1" Duration="0:0:0.1" />
      <DoubleAnimation Storyboard.TargetName="WeatherIndicatorScaleTransform" Storyboard.TargetProperty="ScaleX"
        From="0" To="1" Duration="0:0:0.5" />
      <DoubleAnimation Storyboard.TargetName="WeatherIndicatorScaleTransform" Storyboard.TargetProperty="ScaleY"
        From="0" To="1" Duration="0:0:0.5" />
    </Storyboard>

    <Storyboard x:Key="HideWeatherImageStoryboard" Completed="HideWeatherImageStoryboard_Completed">
      <DoubleAnimation Storyboard.TargetName="weatherIndicatorImage" Storyboard.TargetProperty="Opacity"
        To="0" Duration="0:0:0.1" />
    </Storyboard>

    <DrawingImage x:Key="RainingImageSource" Drawing="{StaticResource RainingDrawingResource}" />
    <DrawingImage x:Key="SunnyImageSource"  Drawing="{StaticResource SunnyDrawingResource}" />
  </Window.Resources>

  <Grid Name="tomorrowsWeather" Margin="20">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Button Name="fetchButton" Content="Fetch Forecast" Click="ForecastButtonHandler"  Grid.Row="0"/>
    <Image  Name="ClockImage"  RenderTransformOrigin="0.5,0.5" Opacity="0" Stretch="Uniform"
      Grid.Row="1" Margin="10" >
      <Image.Source>
        <DrawingImage>
          <DrawingImage.Drawing>
            <DrawingGroup>
              <DrawingGroup.Children>
                <GeometryDrawing>
                  <GeometryDrawing.Geometry>
                    <EllipseGeometry Center="50,50" RadiusX="45" RadiusY="45" />
                  </GeometryDrawing.Geometry>
                  <GeometryDrawing.Brush>
                    <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                      <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="LightGray" />
                        <GradientStop Offset="0.5" Color="#CCCCFF" />
                      </LinearGradientBrush.GradientStops>
                    </LinearGradientBrush>
                  </GeometryDrawing.Brush>
                  <GeometryDrawing.Pen>
                    <Pen Thickness="5" Brush="Black" />
                  </GeometryDrawing.Pen>
                </GeometryDrawing>
                <GeometryDrawing>
                  <GeometryDrawing.Geometry>
                    <LineGeometry StartPoint="50,50" EndPoint="50,10">
                      <LineGeometry.Transform>
                        <RotateTransform x:Name="ClockHandRotateTransform"  Angle="0" CenterX="50" CenterY="50" />
                      </LineGeometry.Transform>
                    </LineGeometry>
                  </GeometryDrawing.Geometry>
                  <GeometryDrawing.Pen>
                    <Pen Brush="Black" Thickness="5" StartLineCap="Round" EndLineCap="Triangle"  />
                  </GeometryDrawing.Pen>
                </GeometryDrawing>
                <GeometryDrawing>
                  <GeometryDrawing.Geometry>
                    <EllipseGeometry Center="50,50" RadiusX="45" RadiusY="45" />
                  </GeometryDrawing.Geometry>
                  <GeometryDrawing.Brush>
                    <RadialGradientBrush GradientOrigin="0.75,0.25">
                      <RadialGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="#99FFFFFF" />
                        <GradientStop Offset="0.5" Color="Transparent" />
                        <GradientStop Offset="0.75" Color="#99FFFFFF" />
                        <GradientStop Offset="1.0" Color="#33000000" />
                      </RadialGradientBrush.GradientStops>
                    </RadialGradientBrush>
                  </GeometryDrawing.Brush>
                </GeometryDrawing>
                <GeometryDrawing>
                  <GeometryDrawing.Geometry>
                    <EllipseGeometry Center="50,50" RadiusX="45" RadiusY="45" />
                  </GeometryDrawing.Geometry>
                  <GeometryDrawing.Brush>
                    <LinearGradientBrush StartPoint="0.75,0.1" EndPoint="0.25, 0.9">
                      <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="#99FFFFFF" />
                        <GradientStop Offset="0.5" Color="Transparent" />
                        <GradientStop Offset="1.0" Color="#99000000" />
                      </LinearGradientBrush.GradientStops>
                    </LinearGradientBrush>
                  </GeometryDrawing.Brush>
                </GeometryDrawing>
              </DrawingGroup.Children>
            </DrawingGroup>
          </DrawingImage.Drawing>
        </DrawingImage>
      </Image.Source>
      <Image.RenderTransform>
        <ScaleTransform x:Name="ClockFaceScaleTransform" />
      </Image.RenderTransform>
    </Image>
 
    <Image  Name="weatherIndicatorImage" Opacity="0" Grid.Row="1" Margin="10" RenderTransformOrigin="0.5,0.5">
      <Image.RenderTransform>
        <ScaleTransform x:Name="WeatherIndicatorScaleTransform" />
      </Image.RenderTransform>
    </Image>

    <TextBlock Name="weatherText"
      HorizontalAlignment="Center"
      Grid.Row="2" />
  </Grid>
</Window>

- behind

//<SnippetThreadingWeatherCodeBehind>
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);
        }
         // 실재적인 Background Thread에 해당 , 코드를 보면 UI Element에 접근하는 코드는 Dispatcher에 Delegate를 넣는부분밖에 없다.

        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. Grid의 dispatcher에 delegate를 insert시켜두면                       
             tomorrowsWeather.Dispatcher.BeginInvoke(
                                   System.Windows.Threading.DispatcherPriority.Normal,
                                  new OneArgDelegate(UpdateUserInterface),  weather);

           
        }


        // 지정된 우선순위에 따라 이 메소드가 호출되는데.. 이미 현재 메소드는 메인프로세스 혹은 UI컨트롤의
        // Dispatcher내에 존재하므로 백그라운드 쓰레드에서 Invoke시켰지만 메인프로세스가 실행시킨다고 보면 될까?
        // 그래야 아래 코드처럼 UI컨트롤들에 접근을 할 수가 있지 않은가?


        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);
        }       
    }
}


[ Multiple Windows, Multiple Threads ]
새창을 열면서 새로운 쓰레드를 매핑하는 예제

이예제를 보면 하나의 윈도우는 하나의 메인쓰레드를 가진다는것은 확실한거 같음 문제는 그 하위의 컨트롤들이 내부적으로 백그라운드 쓰레드를 가질 수 있고 이들은  이전에 언급했던 Dispatcher를 공유하면서 UI쓰레드내에서 돌아갈것인데 이  UI쓰레드가 윈도우를 생성할때의 메인쓰레드와 같은 녀석일까? 코드를 보면 같은녀석이다.라고 판단된다.ㅋ


- XAML

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

   
 

- Behind

//<SnippetThreadingMultiBrowserCodeBehind>
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();

           // 이 코드를 보면 Dispatcher도 하나의 쓰레드 같이 구현되 있는거 같네요.. 메인쓰레드와 상호작용 할 수 있게 구현되어 UI에 접근 하게 구현을 해둬야 이런코드가 가능할듯..

WPF는 새로운 쓰레드에 대해 이쓰레드를 관리 할수 있는 Dispatcher를 자동으로 생성해준다고 하며 이렇게 Run만시키면 된다고 하네요
         }
    }
}
//</SnippetThreadingMultiBrowserCodeBehind>


[ Writing Components Using Threading ]

1. Event-based Asynchronous Pattern에 대한 이해
(  http://msdn2.microsoft.com/en-us/library/wewwczdw.aspx )는 컴퍼넌트가 자신의 행위를 클라이언트에게 어떻게 노출하느냐를 정의하는 패턴이다.(닷넷에서 가장 일반적으로 사용하는 표준패턴임)

거의 아래와 같은 형식의 코드가 작성된다.
public delegate void GetWeatherCompletedEventHandler(object sender,GetWeatherCompletedEventArgs e);

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

여기서 GetWeatherAsync는 위의 코드처럼 이런식의 코드(delegate를 이용하여 백그라운드에서 실행)로 구현될 것이고

 NoArgDelegate fetcher = new NoArgDelegate(this.FetchWeatherFromServer);
 fetcher.BeginInvoke(null, null);

이패턴의 가장 중요한 부분은 MethodNameAsync를 호출한 메인쓰레드에 정의되는 MethodNameCompleted 일 것이다.

2.  WPF에서 non-graphical Component로 이 패턴을 이용하여 fetchWeatherFromServer()를 제공하는 방법
CurrentDispatcher를 DispatcherSynchronizationContext에 저장하게 하여 쉽게 구현할 수 있다??

구현예)
public class WeatherComponent2 : Component
{
    private DispatcherSynchronizationContext requestingContext = null;   
   
    public string GetWeather()
    {
        return fetchWeatherFromServer();
    }

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

        requestingContext = (DispatcherSynchronizationContext)DispatcherSynchronizationContext.Current;

       
       // Launch thread
        NoArgDelegate fetcher = new NoArgDelegate(this.fetchWeatherFromServer);

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

   
 

        //graphical한 경우에 Dispatcher.BeginInvoke를 이요하여 호출하는대신 아래와같은 요상한 방법을 사용

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

        return e.Weather;  // 놀랍게 리턴도 하네요??
    }

    // 이녀석이 메인 쓰레드가 리턴받게되는 함수가 되나 봅니다. 구현의 예제가 있음 확인해봤을텐데..

    // WPF에서 어떻게 이녀석을 사용한다는 건지 알수가 없네요~ 이렇게 하는구나~ 하고 넘어갑니다.

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


    public event
GetWeatherCompletedEventHandler GetWeatherCompleted;
    public delegate string NoArgDelegate();
}

Nested Pumping

위에서 설명했듯이 UI쓰레드가 실행시키는 자식쓰레드(백그라운드)는 메인쓰레드의 UI컨트롤을 직접적으로 접근하는것이 허용되지 않는다. 그래서  동기화를 위해서 Dispatcher큐를 이용하여 백그라운드의 처리결과를 콜백에서 처리하는 방법을 사용하였다.

하지만 백그라운드 쓰레드가 UI쓰레드로 돌아가야 되는 경우가 존재한다.(MessageBox.Show의 새창의 경우,모달다열로그?)
이 경우 확인버튼을 누르기 전에 부모창이 활성화 되지 않지만 부모창에서 painting은 일어나는 것을 확인 할 수 있을것이다.

이렇게 윈도우 메시지의 경우 자식쓰레드가 작동하는 동안 부모쓰레드도 작동을 하는것 처럼 하는 기술이 Nested Pumping이다.

내부적으로 새창호출 시 Dispatcher.PushFrame을 호출하면 애플리케이션의 현재 실행포인터를 저장하고 부모를 페인팅 할 수 있는 새로운 메시지 루프를 시작시켜서 부모의 painting을 처리한다. 팝업의 작업이 끝나면 저장했던 실행포인터를 이용하여 부모가 에게 실행권을 넘긴다. ???? 이름과 구현기술이 좀 매칭이 안되는군요 ?????
Application.DoEvent와 유사하게 동작하는거라고 하는 거 보니 새로운 프레임의  메시지 루프를 시작 시키고 부모의 dispatcher에 중첩시켜서 메시지의 종류에 따라 부모가 실행할지 자식이 실행할지를 결정짖게 하는거 같음

   
 

Stale Routed Events
WPF의 트리를 따라  라우팅되는 이벤트가 좀 불한리한 점도 있다고 불평하는 내용인듯 - 무시합니다

   
 

Reentrancy and Locking

CLR의 경우 Object가 롹이 잡히더라도 높은 우선순위의 쓰레드의 경우는 작동을 롹에 상관없이 실행된다고 하네요?

그래서 대부분의 경우는 괞잖지만 WIN32 message나 COM STA Component와 연계될 때 이상한 에러가 발생할수 있답니다.

그담은 뭔 말인지 함 읽어보삼~ 헉헉


  




구글광고