GSI

Only the original thread that created a view hierarchy can touch its views


이런 오류가 날때가 있다.

무심고 아무 스레드나 코드에 UI 업데이트를 하는 부분을 추가 하면 발생 한다.


단순하게 아래 코드와 같이 실행하면 된다.


해당 클래스에 아래 코드를 추가 한다.

final Handler handler = new Handler()
{
public void handleMessage(Message msg)
{
((InterDrawItemAdapter)list.getAdapter()).notifyDataSetChanged();
}
};

UI를 해야 할때 아래 코드를 추가 한다.

Message message = handler.obtainMessage();
handler.sendMessage(message);

이상.

Posted by gsi
:


    public partial class ucPanel : UserControl
    {
        public ucPanel()
        {
            InitializeComponent();

            //
            backgroundWorker1.RunWorkerAsync();
        }

        static public Font ChangeFontSize2(Font font, float fontSize, GraphicsUnit unit)
        {
            if (font != null)
            {
                float currentSize = font.Size;
                if (currentSize != fontSize)
                {
                    font = new Font(font.Name, fontSize,
                        font.Style, unit,
                        font.GdiCharSet, font.GdiVerticalFont);
                }
            }
            return font;
        }

        public delegate void OnAddNode(string title, int x, int y);
        public void AddNode(string title, int x, int y)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new OnAddNode(this.AddNode), new object[] { title, x, y });
                return;
            }

            // 객체 추가
            Label lbl2 = new Label();
            lbl2.AutoSize = true;
            lbl2.Text = title;
            lbl2.Font = ChangeFontSize2(lbl2.Font, lbl2.Font.Size * 2, GraphicsUnit.Pixel);
            lbl2.Left = x;
            lbl2.Top = y;
            this.Controls.Add(lbl2);

            ucNode ucn = new ucNode();
            ucn.Left = x;
            ucn.Top = y;
            this.Controls.Add(ucn);
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            int breakCount = 0;
            Random rand = new Random();

            while (true)
            {
                //
                this.Invoke(new OnAddNode(this.AddNode), new object[] { "test", rand.Next(10, 738), rand.Next(10, 460) });

                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));

                //
                breakCount++;
                if (breakCount > 100)
                {
                    break;
                }
            }
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

        }
    }

Posted by gsi
:

MultiThread 예제 분석

C++ 2008. 12. 30. 14:56 |

/*  file Main.cpp
 *
 *  This program is an adaptation of the code Rex Jaeschke showed in
 *  Listing 4 of his Nov 2005 C/C++ User's Journal article entitled
 *  "C++/CLI Threading: Part II".  I changed it from C++/CLI (managed)
 *  code to standard C++.
 *
 *  One hassle is the fact that C++ must employ a free (C) function
 *  or a static class member function as the thread entry function.
 *
 *  This program must be compiled with a multi-threaded C run-time
 *  (/MT for LIBCMT.LIB or /MTd for LIBCMTD.LIB).
 *
 *                                      John Kopplin  7/2006
 */

#include <stdio.h>
#include <windows.h>          // for HANDLE
#include <process.h>          // for _beginthread()

static bool interlocked = false;    // change this to fix the problem

const int maxCount = 100000000;
static LONG value = 0;              // under Windows Server 2003 you
                                    // could use LONGLONG here

unsigned __stdcall TMain(void* arg)
{
    if ( interlocked )
    {
      for ( int i = 1; i <= maxCount; i++ )
      {
        InterlockedIncrement(&value); // under Windows Server 2003 you
                                      // could use InterlockedIncrement64() here
      }
    }
    else
    {
      for ( int i = 1; i <= maxCount; i++ )
      {
        ++value;
      }
    }

    return 3;  // thread exit code
}


int main()
{
    // In this program we create 3 threads and request that their
    // entry-point-function be the TMain() function which is a
    // free (C) function and hence causes no problems for
    // _beginthreadex()

   
    HANDLE   hth1;
    unsigned  uiThread1ID;

CreateThread() 함수를 사용했을 때 스레드에서 Win32 함수만을 사용한다면 문제가 없지만 C 런타임 라이브러리 호출을 하려면 _beginthread()나 _beginthreadex() 를 사용해야 한다.

(원형) unsigned long _beginthread( void (*lpThreadEntryPoint)(void* lpArgList), unsigned uStackSize, void* lpArgList );

lpThreadEntryPoint 는 스레드가 시작된 함수의 주소인데, 이 함수에는 CreateThread()와 같이 한 개의 32비트 인자인 lpArgList가 있으며 이 값을 _beginthread()에 전달해야 한다. 이 함수는 void 타입으로 정의되어 CreateThread()와 조금 차이가 있다. 이 함수는 종료시까지 실행되는데 값을 반환하지는 않는다. uStackSize는 CreateThread()에서와 동일한 의미를 가지는데 스레드 스택의 베이스에서 커멧될 바이트 수이며, 이 값을 0으로 하면 윈도우는 부모 스레드에서 커멧된 것과 동일한 양의 메모리를 커멧힌다.

_beginthreadex() 는 CreateThread()와 완전히 같고 SECURITY_ATTRIBUTES 포인터와 시작 플래그(0이나 CREATE_SUSPENDED), 스레드 ID를 받는 포인터를 인자로 받는다. 일시 정지된 상태에서 스레드를 시작하거나 PostThreadMessage()에 스레드 ID를 사용하려면 _beginthreadex()를 사용해야 한다. _beginthread()
로 시작한 스레드만이 종료 코드를 설정할 수 있으므로 이때에도 필요하다.

    hth1 = (HANDLE)_beginthreadex( NULL,         // security
                                   0,            // stack size
                                   TMain,        // entry-point-function
                                   NULL,         // arg list
                                   CREATE_SUSPENDED,  // so we can later call ResumeThread()
                                   &uiThread1ID );

    if ( hth1 == 0 )
        printf("Failed to create thread 1\n");

    DWORD   dwExitCode;

 스레드가 수행되는 동안에 GetExitCodeThread()를 호출하면 종료 코드 STILL_ACTIVE를 반환한다.
 스레드에서 return 문이 등장하면 윈도우는 ExitThread()를 대신 호출해서 return 문에 전달된 값을 전달해 주는데, 이 때문에 입구 함수는 DWORD의 반환값을 갖는다.
 스레드를 시작하는 방법이 여러가지 있고 각각의 종료 함수도 다르므로 스레드에서는 단순히 return문을 사용하는 것이 좋다.

    GetExitCodeThread( hth1, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259
    printf( "initial thread 1 exit code = %u\n", dwExitCode );

    HANDLE   hth2;
    unsigned  uiThread2ID;

    hth2 = (HANDLE)_beginthreadex( NULL,         // security
                                   0,            // stack size
                                   TMain,        // entry-point-function
                                   NULL,         // arg list
                                   CREATE_SUSPENDED,  // so we can later call ResumeThread()
                                   &uiThread2ID );

    if ( hth2 == 0 )
        printf("Failed to create thread 2\n");

    GetExitCodeThread( hth2, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259
    printf( "initial thread 2 exit code = %u\n", dwExitCode );

    HANDLE   hth3;
    unsigned  uiThread3ID;

    hth3 = (HANDLE)_beginthreadex( NULL,         // security
                                   0,            // stack size
                                   TMain,        // entry-point-function
                                   NULL,         // arg list
                                   CREATE_SUSPENDED,  // so we can later call ResumeThread()
                                   &uiThread3ID );

    if ( hth3 == 0 )
        printf("Failed to create thread 3\n");

    GetExitCodeThread( hth3, &dwExitCode );  // should be STILL_ACTIVE = 0x00000103 = 259
    printf( "initial thread 3 exit code = %u\n", dwExitCode );

    // If we hadn't specified CREATE_SUSPENDED in the call to _beginthreadex()
    // we wouldn't now need to call ResumeThread().

인자 dwFlags는 스레드의 초기 스케쥴링을 결정하는데 0의 값으로 설정하면 윈도우는 스레드가 바로 수행되도록 스케쥴 하고, 0이 아닌 CREATE_SUSPENDED의 값은 핸들을 ResumeThread()에 전달할 때까지 스레드가 실행되지 않도록 한다.

    ResumeThread( hth1 );   // Jaeschke's   // t1->Start();
    ResumeThread( hth2 );   // Jaeschke's   // t2->Start();
    ResumeThread( hth3 );   // Jaeschke's   // t3->Start();

    // In C++ the process terminates when the primary thread exits
    // and when the process terminates all its threads are then terminated.
    // Hence if you comment out the following waits, the non-primary
    // threads will never get a chance to run.

    WaitForSingleObject( hth1, INFINITE );  // Jaeschke's t1->Join()
    WaitForSingleObject( hth2, INFINITE );  // Jaeschke's t2->Join()
    WaitForSingleObject( hth3, INFINITE );  // Jaeschke's t3->Join()

    GetExitCodeThread( hth1, &dwExitCode );
    printf( "thread 1 exited with code %u\n", dwExitCode );

    GetExitCodeThread( hth2, &dwExitCode );
    printf( "thread 2 exited with code %u\n", dwExitCode );

    GetExitCodeThread( hth3, &dwExitCode );
    printf( "thread 3 exited with code %u\n", dwExitCode );

    printf( "After %d operations, value = %d\n", 3 * maxCount, value );
                                         // under Windows Server 2003 you
                                         // could use %I64d

    // The handle returned by _beginthreadex() has to be closed
    // by the caller of _beginthreadex().

대부분의 스레드 관리 함수는 스레드 핸들을 필요로 하는데 그 중 몇몇만이 스레드 ID를 필요로 하고 가장 필요로 하는 함수는 PostThreadMessage()이다. 모든 Win32 핸들과 같이 스레드 핸들의 사용이 끝나면 CloseHandle() 함수를 사용하여 스레드 핸들을 닫아야 한다. 이것은 스레드 자체에는 아무런 영향을 미치지 않는다. 스레드 핸들을 닫는 것은 스레드를 끝내는(Kill) 것과는 다르며, 이렇게 하지 않을 경우 프로그램이 종료할 때까지 시스템 자원이 할당된 채로 남게 된다.

    CloseHandle( hth1 );
    CloseHandle( hth2 );
    CloseHandle( hth3 );

    printf("Primary thread terminating.\n");
}

Posted by gsi
:

Thread를 두개를 사용할 경우 하나의 함수를 호출한다고 가정해 보자.
그렇게 되면 다량의 데이터를 효과적으로 여러개의 스레드를 사용해서 처리할 수가 있다.
아래의 예제를 보면 AddItems()를 스레드 두개를 사용해서 처리 되고 있다.
하지만 결과를 보면 100개의 데이터 값만 뿌려지는 것을 알수 있다.

이때 사용하게 되는 것이 lock 구문이다. 이 구문을 사용해서 멀티 스레드에서의
데이터를 안전하게 처리할 수 있다.
lock 에 포함되는 것은 동기화에 처리되어야 할 데이터를 락을 걸어 주게 되는 것으로
여겨진다.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ConsoleApplication4
{
    class Program
    {
        static List<string> list = new List<string>();

        static void Main(string[] args)
        {
            new Thread(AddItems).Start();
            new Thread(AddItems).Start();
        }

        static void AddItems()
        {
            for (int i = 0; i < 100; i++)
                lock (list)
                {
                    string _str = "Item " + list.Count + " ID=" + AppDomain.GetCurrentThreadId();
                    list.Add(_str);
                    Console.WriteLine(_str);
                }
        }
    }
}




 

Posted by gsi
:

PathInfo.cs
----------------------------
using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace GsiImageView
{
    public class PathInfo
    {
        private string _path;
        private string _filename;

        public string Path
        {
            get { return _path; }
            set { _path = value; }
        }

        public string FileName
        {
            get { return _filename; }
            set { _filename = value; }
        }

        public PathInfo(string path, string filename)
        {
            this._path = path;
            this._filename = filename;
        }
    }

    public class PathInfoList : ObservableCollection<PathInfo>
    {
        public PathInfoList()
        {
            //최초 입력
            Add(new PathInfo(@"D:\WPF_Project\GsiImageView\Image\1111.jpg", "1111.jpg"));
            Add(new PathInfo(@"D:\WPF_Project\GsiImageView\Image\2222.jpg", "2222.jpg"));
        }
    }
}

Window1.xaml
-------------------------------
<Window
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:c="clr-namespace:GsiImageView"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:GsiImageView="clr-namespace:GsiImageView"
 x:Class="GsiImageView.Window1"
 x:Name="Window"
 Title="Gsi ImageViewer"
 Width="640" Height="480">

 <Window.Resources>
        <c:PathInfoList x:Key="pathData" />
        <ObjectDataProvider x:Key="PathInfoListDS" d:IsDataSource="True" ObjectType="{x:Type GsiImageView:PathInfoList}"/>
  <DataTemplate x:Key="PathInfoListTemplate">
   <DataTemplate.Resources>
    <Storyboard x:Key="OnLoaded1">
     <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="stackPanel" Storyboard.TargetProperty="(UIElement.Opacity)">
      <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.1"/>
      <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
     </DoubleAnimationUsingKeyFrames>
    </Storyboard>
   </DataTemplate.Resources>
   <StackPanel HorizontalAlignment="Stretch" Margin="0,2,2,2" x:Name="stackPanel" VerticalAlignment="Stretch" RenderTransformOrigin="0.5,0.5">
    <StackPanel.Background>
     <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
      <GradientStop Color="#FFA1A1A1" Offset="0"/>
      <GradientStop Color="#FFFFFFFF" Offset="1"/>
     </LinearGradientBrush>
    </StackPanel.Background>
    <StackPanel.RenderTransform>
     <TransformGroup>
      <ScaleTransform ScaleX="1" ScaleY="1"/>
      <SkewTransform AngleX="0" AngleY="0"/>
      <RotateTransform Angle="0"/>
      <TranslateTransform X="0" Y="0"/>
     </TransformGroup>
    </StackPanel.RenderTransform>
    <TextBlock HorizontalAlignment="Stretch" Margin="0,2,0,2" VerticalAlignment="Stretch" Padding="4,0,0,0" Text="{Binding Path=FileName}"/>
    <Border Height="Auto" BorderBrush="#FFCFD6FF" BorderThickness="2,2,2,2" Width="{Binding ElementName=Slider_ImageSize, Path=Value}">
     <Image Width="Auto" Height="Auto" Source="{Binding Path=Path}"/>
    </Border>
   </StackPanel>
   <DataTemplate.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded" SourceName="stackPanel">
     <BeginStoryboard Storyboard="{StaticResource OnLoaded1}"/>
    </EventTrigger>
   </DataTemplate.Triggers>
  </DataTemplate>
  <ItemsPanelTemplate x:Key="ItemsPanelTemplate1">
   <WrapPanel/>
  </ItemsPanelTemplate>
 </Window.Resources>

 <Window.Background>
  <LinearGradientBrush EndPoint="0.988,0.988" StartPoint="0.008,0.054">
   <GradientStop Color="#FFB5B5B5" Offset="0"/>
   <GradientStop Color="#FFFFFFFF" Offset="1"/>
  </LinearGradientBrush>
 </Window.Background>

 <Grid x:Name="LayoutRoot">
  <Grid.RowDefinitions>
   <RowDefinition Height="30.192"/>
   <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <Label HorizontalContentAlignment="Center" Padding="0,0,0,0" VerticalContentAlignment="Center" HorizontalAlignment="Left" Margin="0,0,0,0" Width="43" Content="경로 :"/>
  <TextBox Background="{x:Null}" BorderBrush="#7F939393" BorderThickness="2,2,2,2" HorizontalContentAlignment="Left" Padding="0,1,0,1" VerticalContentAlignment="Center" Margin="43,0,0,0" x:Name="DirBox" Text="TextBox" TextWrapping="Wrap" HorizontalAlignment="Left" Width="59"/>
  <Button HorizontalAlignment="Right" Margin="0,0,0,0" x:Name="FolderOpen" Width="63" Content="테스트" Click="OnFolderOpenClick"/>
  <ListBox IsSynchronizedWithCurrentItem="True" Background="{x:Null}" Margin="4,4,4,4" x:Name="ImgListBox" Grid.Row="1" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemTemplate="{DynamicResource PathInfoListTemplate}" ItemsPanel="{DynamicResource ItemsPanelTemplate1}" ItemsSource="{Binding Mode=OneWay, Source={StaticResource pathData}}"/>
  <Slider Margin="109,0,70,0" x:Name="Slider_ImageSize" VerticalAlignment="Center" Maximum="1024" Minimum="10" Value="100"/>
 </Grid>
</Window>

Window1.xaml.cs
-----------------------------
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Threading;

namespace GsiImageView
{
 public partial class Window1
 {
        public delegate void UpdateImage(string _path);

  public Window1()
  {
   this.InitializeComponent();
   
   // Insert code required on object creation below this point.

            //ObjectDataProvider dp = (ObjectDataProvider)this.FindResource("PathInfoListDS");
            //pathInfolist = (PathInfoList)dp.Data;
  }

        private void OnFolderOpenClick(object sender, RoutedEventArgs e)
        {
            //ImgListBox.Items.Add(new PathInfo(@"D:\WPF_Project\GsiImageView\Image\3333.jpg", "3333.jpg"));

            //Window1 w1 = new Window1();
            Thread loadThread = new Thread(new ThreadStart(DataLoad));
            loadThread.Start();
            Thread.Sleep(1);
        }

        private void DataLoad()
        {
            //디렉토리 경로 가져 오기
            DirectoryInfo di = new DirectoryInfo(@"D:\WPF_Project\GsiImageView\Image\MaxImageTest");
            try
            {
                if (di.Exists)
                {
                    //경로가 있음
                    foreach (FileInfo fi in di.GetFiles())
                    {
                        //경로를 타고 세부 이미지 이름을 얻어 온다.
                        string path = di.FullName + "\\" + fi.Name;
                        //il.Add(new PathInfo(path, fi.Name));

                        this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.DataBind, new UpdateImage(UpdateImageList), path);

                        Thread.Sleep(60);
                    }
                }
                else
                {
                    MessageBox.Show("경로가 확실하지 않습니다.");
                    return;
                }
            }
            catch
            {
                MessageBox.Show("경로가 확실하지 않거나, 비정상 오류 입니다.");
                return;
            }
        }

        public void UpdateImageList(string _path)
        {
            PathInfoList pathinfolist = (PathInfoList)this.FindResource("pathData");
            pathinfolist.Add(new PathInfo(_path, "none.jpg"));
//            Thread.Sleep(1);
/*
            //pathInfolist.Add(new PathInfo(_path, "none"));
            ImgListBox.Items.Add(new PathInfo(_path, "none"));
            ImgListBox.UpdateLayout();
            try
            {
                int iii = 0;
            }
            catch(Exception e)
            {
                int iii = 0;
            }
 */
        }
 }
}

Posted by gsi
:

멀티 스레드.. 정리..

C# 2007. 9. 12. 12:59 |

메세지큐에 모든 동작 상태들을 담게 된다.
그리고 그 내용을 메인 스레드에서 실제로 작업 해도 담당하게 된다.

하지만 여기서 다른 스레드와의 동기 여부가 문제가 되며,
다른 스레드에서는 메인스레드에서 생성된 인스턴스된 객체들에 바로 제어를 하지 못한다.

그래서 다른 스레드에서 하는 작업을 메세지 큐에 집어 넣고 메인 스레드가 작업은 해야 한다.
그렇기 때문에 여기서 나오는 내용들이
Invoke, BeginInvoke 가 나오게 된다.

Invoke 해당 내용이 업데이트 될때까지 기다렸다가 다음 행을 시작하게 되며,
BeginInvoke 비동기 적으로 바로 다음 행으로 진행이 되며, 나중에 업데이트가 되게 된다.

...

메인 스레드에게 이러한 작업을 실행해라 하고 다른 스레드에서 위임을 시켜 줘야 한다.

//핸들러 설정
public delegate void UpdateUIHandler(DataSet _dsTmp);

public void GetTable()
{
   Console.Write(...);
   string SQLCommentText = ...
   ....
   Thread.Sleep(1000 * 5);

   //TargetGrid가 태어난 스레드에서 실행을 하게 된다.
   TargetGrid.Invoke(new UpdateUIHandler(UpdateUI), new object[] {_dsTmp});
}

public void UpdateUI(DataSet _dsTmp)
{
   TargetGrid.DataSource = _dsTmp.Tables[0];
}

실행을 하게 되면
내가 다르게 만든 스레드에서 UpdateUI를 하게 되지만 UpdateUI는 메인 스레드에서
작업 하게 된다.

윈도우 어플리케이션에서는 스레드를 사용하지만 다른 스레드에서 사용하지 않고
메인 스레드로 위임 해서 꼭 사용해야 한다.
이럴때 사용하는게 Invoke()가 된다.

BeginInvoke를 Invoke를 사용하게 되면
다음 행은 바로 실행 되고, UpdateUI를 실행하는 스레드는 따로 진행하게 된다.

Posted by gsi
:

공도님이 소개해준 스레드 강좌.. 좋네욤.. 7편까지 있음..
http://vismuri.com/blog_category_12.aspx
Posted by gsi
: