2009-10-19 3 views
1

연습 문제로 .NET 3.5 WPF 응용 프로그램에서 백그라운드 스레드로 실행되는 카운터를 생성합니다. 카운터가 증가하고 몇 초마다 합계가 화면에 출력된다는 아이디어가 있습니다..NET에서 멀티 스레딩 - 백그라운드에서 카운터 구현

연습 문제이므로 전경 스레드는 실제로 아무 것도하지 않지만 그것이 실제로 일어난다 고 상상해보십시오!

카운터를 시작하고 중지하는 시작 및 중지 버튼이 있습니다.

문제는 포어 그라운드 스레드가 몇 초마다 총을 폴링하여 양식의 텍스트 상자에서 업데이트하도록하려는 경우입니다. 이 앱을 사용하면 전체 앱이 '응답 없음'상태가됩니다. 현재는 카운트를 시작한 후 고정 된 시간에만 업데이트하도록했습니다.

public partial class Window1 : Window 
    { 
     public delegate void FinishIncrementing(int currentCount); 
     private int reportedCount; 
     private Thread workThread; 

     public Window1() 
     { 
      InitializeComponent(); 
     } 

     #region events 

     private void Window_Loaded(object sender, RoutedEventArgs e) 
     { 
      txtCurrentCount.Text = "0"; 
      reportedCount = 0; 
      InitialiseThread(); 
     } 

     private void btnStart_Click(object sender, RoutedEventArgs e) 
     { 
      workThread.Start(); 

      // Do some foreground thread stuff 

      Thread.Sleep(200); // I want to put this in a loop to report periodically 
      txtCurrentCount.Text = reportedCount.ToString(); 
     } 

     private void btnStop_Click(object sender, RoutedEventArgs e) 
     { 
      workThread.Abort(); 
      txtCurrentCount.Text = reportedCount.ToString(); 
      InitialiseThread(); 
     } 

     #endregion 

     private void InitialiseThread() 
     { 
      WorkObject obj = new WorkObject(ReportIncrement, reportedCount); 
      workThread = new Thread(obj.StartProcessing); 
     } 

     private void ReportIncrement(int currentCount) 
     { 
      reportedCount = currentCount; 
     } 
    } 

    public class WorkObject 
    { 
     private Window1.FinishIncrementing _callback; 
     private int _currentCount; 

     public WorkObject(Window1.FinishIncrementing callback, int count) 
     { 
      _callback = callback; 
      _currentCount = count; 
     } 

     public void StartProcessing(object ThreadState) 
     { 
      for (int i = 0; i < 100000; i++) 
      { 
       _currentCount++; 

       if (_callback != null) 
       { 
        _callback(_currentCount); 
        Thread.Sleep(100); 
       } 
      } 
     } 
    } 

내 질문은 : 어떻게 그것을 정기적으로보고받을 수 있나요, 내가하려고 할 때 왜 실패하는

난 당신이 전체 그림을 얻을 수 있도록, 여기에 모든 코드를 덤프합니다 보고를 for 루프에 넣으려면?

편집 : 내 훈련은 멀티 스레딩의 구식 방법으로 자신을 익숙하게하는 것이라고 언급 한 경우, 그래서 BackgroundWorker을 사용할 수 없습니다!

답변

3

Windows 응용 프로그램의 모든 형태는 다음과 같은 형태의 메시지 루프를 가지고있다.

void WndProc(Message msg) 
{ 
    switch (msg.Type) 
    { 
     case WM_LBUTTONDOWN: 
      RaiseButtonClickEvent(new ButtonClickEventArgs(msg)); 
      break; 

     case WM_PAINT: 
      UpdateTheFormsDisplay(new PaintEventArgs(msg)); 
      break; 

     case WM_xxx 
      //... 
      break; 

     default: 
      MakeWindowsDealWithMessage(msg); 
    } 
} 

을이이 창을 한 번에 하나의 메시지/이벤트를 처리 할 수 ​​있음을 의미 볼 수 있듯이 : WPF는 이런 식 WinProc의 기본 구현을 제공합니다. 버튼 클릭 이벤트에서 잠 들어있을 때 메시지 루프가 멈추고 응용 프로그램이 응답을 멈 춥니 다 (메시지 가져 오기 및 처리).

DispatcherTimer을 사용하면 창에 주기적으로 WM_TIMER (Tick) 메시지를 창 메시지 대기열에 넣으라는 메시지가 표시됩니다. 이렇게하면 응용 프로그램이 다른 스레드를 사용하지 않고 일정한 간격으로 작업을 수행하면서 다른 메시지를 처리 ​​할 수 ​​있습니다.

더 자세히 들어 MSDN을 참조 "About Messages and Message Queues"

+0

그럼 매일 매일 새로운 것을 배웁니다. 감사합니다. –

1

BackgroundWorker 개체를 사용할 수 있습니다. 귀하가 가입 한 이벤트를 통해보고 진행 상황을 지원합니다. 그런 식으로 백그라운드 작업의 상태에 대한 폴링 폴링을 할 필요가 없습니다. 사용하기 쉽고 응용 프로그램에 구현하기 쉽습니다. 또한 WPF 발송자와도 잘 어울립니다.

이유는 귀하의 앱에서 "응답 없음"메시지가 나타나는 이유입니다. 그 Thread.Sleep() 당신의 버튼 프레스 핸들러에 있습니다. 그것은 UI 스레드가 멈추도록 말하고 있습니다. 그러면 응용 프로그램이 "응답 없음"메시지를 보내는 메시지 처리를 중단합니다.

+0

+1 - WinForms에서 스레딩을 할 때 BackgroundWorker가 가장 먼저 시작됩니다. –

0

나는 조금 다른 것을 시도 할 것이다. 호출 프로그램이 작업자 스레드를 폴링하는 대신 작업자 스레드가 주기적으로 이벤트를 발생시켜 호출 스레드로 다시보고하도록합니다. 예를 들어, 카운팅 루프에서 현재 카운트가 1000으로 나눌 수 있는지 확인하고, 그렇지 않은 경우 이벤트를 발생시킵니다. 귀하의 호출 프로그램은 이벤트를 포착하여 디스플레이를 업데이트 할 수 있습니다.

2

루프를 사용 중이므로 "응답 없음"이됩니다.

대체는 A 타이머 루프 모두가 다시 작동합니다 : P

것은 그것이 결코 클릭 이벤트를 종료하지 바쁜 메인 스레드를 유지 있도록 메인 스레드에서 루프를 사용하고 있습니다.

주 스레드가 루프에서 사용 중이므로 창 메시지 (이는 창 입력 및 그리기를 처리하는 데 사용되는 다른 메시지)를 처리 할 수 ​​없습니다. Windows에서 응용 프로그램이 메시지를 처리하지 않는다는 것을 감지 할 때마다 "응답 없음"으로보고합니다.

+0

루프로 인해이 문제가 발생하는 이유는 무엇입니까? 루프에 잠 들어 있기 때문에 끊임없이 업데이트되는 것은 아닙니다. –

+1

Thread.Sleep은 스레드를 잠자기 상태로 두어 Windows 메시지 처리기가 실행되지 않으므로 응답이 없습니다 –

2

폴링을 루프에 넣을 때 일어나는 일은 모든 Windows 메시지 처리 (그리기, 이동, 최소화/최대화, 키보드/마우스 입력 메시지)를 수행하는 Dispatcher로 제어권을 되돌려 보내지 않도록한다는 것입니다. , 등 ...). 즉, 응용 프로그램이 Windows에서 보내는 메시지에 응답하지 않아 "응답 없음"으로 표시됩니다.

코드를 재구성하여 원하는 동작을 얻을 수있는 몇 가지 방법이 있습니다. 다른 사람들이 언급했듯이 텍스트 상자를 업데이트하는 GUI 스레드에서 타이머를 만들 수 있습니다. DispatcherTimer을 만들고 TickInterval 속성을 설정하면됩니다.

ReportIncrement이 호출 될 때마다 Control.Invoke을 호출하고 텍스트 상자를 업데이트하는 함수를 전달하여 GUI를 업데이트 할 수도 있습니다. 예를 들어 :

while (NotTimeToCloseForm) 
{ 
    Message msg = GetMessageFromWindows(); 
    WndProc(msg); 
} 

이 모든 Windows 응용 프로그램뿐만 아니라 닷넷 사람에 적용

private void ReportIncrement(int currentCount) 
    { 
     reportedCount = currentCount; 
     txtCurrentCount.Invoke(
      new Action(()=>txtCurrentCount.Text = reportedCount.ToString()) 
     ); 
    } 
+0

+1 이것은 아마도 가장 일반적으로 유용한 방법입니다. 타이머는 아무것도 변경되지 않았더라도 규칙적인 간격으로 틱합니다. 여기서 사용 된 'Invoke'는 특별히 묻는 경우에만 GUI를 업데이트합니다. –

1

마틴 브라운의 예는 작업을 수행하는 동안 응용 프로그램이 여전히 그것의 수신 WM_TICK 메시지를 다른 모든 창 메시지와 같은 큐에 따라서는 동기 만들기.

다음은 "true multi threading"의 예입니다. 여기서 프로세스는 다른 스레드를 생성하고 스레드는 업데이트 값을 호출하여 레이블 값을 업데이트합니다.

public partial class Window1 : Window 
{ 
    Thread _worker; 

    public Window1() 
    { 
     InitializeComponent(); 
     _worker = new Thread(new ThreadStart(this.DoWork)); 
     _worker.Start(); 
    } 

    private void DoWork() 
    { 
     WorkObject tWorker = new WorkObject((MyUpdateDelegate)delegate (int count) { this.UpdateCount(count); } , 0); 
     tWorker.StartProcessing(); 
    } 

    public delegate void MyUpdateDelegate (int count); 

    private void UpdateCount(int currentCount) 
    { 
     if (Dispatcher.CheckAccess()) 
     { 
      lblcounter.Content = currentCount; 
     } 
     else 
     { 
      lblcounter.Dispatcher.Invoke((MyUpdateDelegate)delegate (int count) 
      { 
       this.UpdateCount(count); 
      }, currentCount); 
     } 
    } 

    protected override void OnClosing(System.ComponentModel.CancelEventArgs e) 
    { 
     if (_worker != null) 
     { 
      _worker.Abort(); 
     } 
     base.OnClosing(e); 
    } 
} 

public class WorkObject 
{ 
    private Window1.MyUpdateDelegate _callback; 
    private int _currentCount; 

    public WorkObject(Window1.MyUpdateDelegate callback, int count) 
    { 
     _callback = callback; 
     _currentCount = count; 
    } 

    public void StartProcessing() 
    { 
     for (int i = 0; i < 100000; i++) 
     { 
      _currentCount++; 

      if (_callback != null) 
      { 
       _callback(_currentCount); 
       Thread.Sleep(100); 
      } 
     } 
    } 
} 

"올드 스쿨"스레딩은 물론 "올드 스쿨"에 대한 귀하의 정의에 달려 있습니다.

편집 : 마틴과 나의 예가 기능적으로 동일하다는 것을 지적 할 수 있습니다.