2010-07-30 2 views
16

저는 현재 측정 장치의 위치를 ​​제어 할 응용 프로그램을 작성하고 있습니다. 하드웨어가 포함되어 있기 때문에 전동기를 가동하면서 현재 위치 값을 지속적으로 폴링해야합니다. 나는이 클래스에 대한 책임을 지우려고 노력 중이며, 백그라운드 스레드에서 폴링을 수행하고 원하는 위치에 도달하면 이벤트를 발생시킵니다. 아이디어는 폴링이 나머지 응용 프로그램이나 GUI를 차단하지 않는다는 것입니다. 새로운 Threading.Task.Task 클래스를 사용하여 백그라운드 스레드 배관을 모두 처리하려고했습니다..Net 왜 Threading.Task.Task는 여전히 내 UI를 차단합니까?

아직 하드웨어가 없지만이 동작을 시뮬레이트하기 위해 테스트 스텁을 빌드해야합니다. 그러나 이와 같은 응용 프로그램을 실행하면 GUI가 여전히 차단됩니다. 아래 코드의 간단한 예를 참조하십시오 (완전하지 않으며 장치 제어를 위해 별도의 클래스를 사용하지 않음). 코드에는 일련의 측정 단계가 있으며 응용 프로그램은 각 단계마다 위치를 정한 다음 측정해야합니다.

public partial class MeasurementForm: Form 
{ 
    private MeasurementStepsGenerator msg = new MeasurementsStepGenerator(); 
    private IEnumerator<MeasurementStep> steps; 

    // actually through events from device control class 
    private void MeasurementStarted() 
    { 
     // update GUI 
    } 

    // actually through events from device control class 
    private void MeasurementFinished() 
    { 
     // store measurement data 
     // update GUI 
     BeginNextMeasurementStep(); 
    } 

    private void MeasurementForm_Shown(object sender, EventArgs e) 
    { 
     steps = msg.GenerateSteps().GetEnumerator(); 
     BeginNextMeasurementStep(); 
    }   
    ... 
    ... 

    private void BeginNextMeasurementStep() 
    { 
     steps.MoveNext(); 
     if (steps.Current != null) 
     { 
      MeasurementStarted(); 
      MeasureAtPosition(steps.Current.Position); 
     } 
     else  
     { 
      // finished, update GUI 
     } 
    } 

    // stub method for device control (actually in seperate class) 
    public void MeasureAtPosition(decimal position) 
    { 
     // simulate polling 
     var context = TaskScheduler.FromCurrentSynchronizationContext(); 
     Task task = Task.Factory.StartNew(() => 
     { 
      Thread.Sleep(sleepTime); 
     }, TaskCreationOptions.LongRunning) 
     .ContinueWith(_ => 
     { 
      MeasurementFinished(); 
     }, context); 
    } 
} 

나는 즉시 메인 스레드와 GUI로 제어 반환이 차단되지 않도록 작업이 백그라운드 스레드에 Thread.sleep를 명령을 실행하는 데 기대. 그러나 GUI는 여전히 차단됩니다. 작업이 주 스레드에서 실행되는 것과 같습니다. 내가 여기서 잘못하고있는 것에 대한 아이디어가 있습니까?

감사

+0

Thread.Sleep 대신 무거운 알고리즘으로 테스트했는데 모든 것이 잘 작동합니다. Thread.Sleep은 항상 주 스레드에서 작동합니까? 나는 그것이 현재 스레드에서 작동 할 것으로 기대합니다. – Stefan

+0

UI 스레드가 아니라 작업 스레드를 차단할 것으로 예상됩니다. – Stefan

+0

무거운 알고리즘이나 Thread.Sleep()을 사용하는지 여부에 관계없이 ContinueWith()와 일련의 작업을 실행하면 UI가 차단된다는 것을 알게되었습니다. 병렬 작업을 실행할 때 무거운 알고리즘에 대해서는이 작업을 수행하지 않지만 그렇게 할 수는 없으며 작업을 순서대로 실행해야합니다. – Stefan

답변

13

당신의 연속 작업 때문에 (ContinueWith를 통해)는 TPL 다른 모든 작업에 관계없이 실제로 지정 여부의 호출 스택 아래로 더 쫓겨 것을 사용하는 TaskScheduler을 지정합니다. 즉, ContinueWith에 지정된 Action 대리인에서 발생하는 Task.Factory.StartNew을 호출하면 기본적으로 지정된 TaskScheduler이 자동으로 사용됩니다.

나는 무슨 일이 벌어지고 있는지 더 잘 보여주기 위해 코드를 수정했습니다.

private void BeginOperation() 
{ 
    System.Diagnostics.Trace.WriteLine("BeginOperation-top " + Thread.CurrentThread.ManagedThreadId); 
    var context = TaskScheduler.FromCurrentSynchronizationContext(); 
    Task task = Task.Factory.StartNew(() => 
    { 
     System.Diagnostics.Trace.WriteLine(" BeginOperation-StartNew-top " + Thread.CurrentThread.ManagedThreadId); 
     Thread.Sleep(5000); 
     System.Diagnostics.Trace.WriteLine(" BeginOperation-StartNew-bottom " + Thread.CurrentThread.ManagedThreadId); 
    }, TaskCreationOptions.LongRunning) 
    .ContinueWith(_ => 
    { 
     System.Diagnostics.Trace.WriteLine(" BeginOperation-ContinueWith-top " + Thread.CurrentThread.ManagedThreadId); 
     EndOperation(); 
     System.Diagnostics.Trace.WriteLine(" BeginOperation-ContinueWith-bottom " + Thread.CurrentThread.ManagedThreadId); 
    }, context); 
    System.Diagnostics.Trace.WriteLine("BeginOperation-bottom " + Thread.CurrentThread.ManagedThreadId); 
} 

private void EndOperation() 
{ 
    System.Diagnostics.Trace.WriteLine("EndOperation-top " + Thread.CurrentThread.ManagedThreadId); 
    BeginOperation(); 
    System.Diagnostics.Trace.WriteLine("EndOperation-bottom " + Thread.CurrentThread.ManagedThreadId); 
} 

나는 리플렉터를 통해 ContinueWith의 코드를 검사하고 나는 호출자가 사용하는 실행 컨텍스트를 발견하려고 시도하는 것을 확인할 수 있습니다. 예, 믿거 나 말거나, 반대되는 자연스러운 직감에도 불구하고 그것이 정확히 무엇을하고 있는지를 보여줍니다.

더 나은 해결책은 아마도 하드웨어 폴링을 수행하는 전용 스레드를 갖는 것입니다.

+0

우와 브라이언, 그쪽 남자 야! 아, 나는 내가 그렇게 재귀 적으로 만들고 있다는 것을 깨닫지 못했다. 작동하지 않는 이유에 대해 이미 답변 할 수 있었기 때문에 대답으로 표시했습니다 (질문). 해결책을 찾는 것이 좋겠지 만 이미 포기하고 다른 방식으로 했으므로 지금은 더 많은 관심사가 있습니다 :-) – Stefan

+0

저에게 잘 어울립니다! 대답을 주셔서 감사합니다 :) 편집 : 분명히 나는 ​​당신에게 현상금, heh을 수여하기 위해 8 시간을 기다려야합니다. –

+0

@Daniel : 솔직히, 나는 현상금에 대해 덜 신경 쓸 수 있습니다. 네가 스스로 지켜줄 수있는 방법이 있었으면 좋겠어.하지만 내가 규칙을 올바르게 읽으면, 시스템은 당신이 무엇이든지간에 당신을 도킹시킬 것입니다. 죄송합니다 : ( –

12

Brian Gideon이 문제의 원인에 대해 정확합니다. - 재귀 적으로 생성 된 작업은 현재 스레드 스케줄러를 SynchronizationContextTaskScheduler으로 설정하여 시작합니다.이 스레드는 주 스레드를 지정합니다. 메인 스레드에서 실행하는 것은 분명히 당신이 원하는 것이 아닙니다.

TaskFactory.StartNew에 대한 오버로드 중 하나를 사용하여 작업 스케줄러를 허용하고이를 TaskScheduler.Default으로 전달하여 문제를 해결할 수 있습니다.

+0

답변이 조금 더 중요하다고 생각합니다. 문제는 다음과 같습니다. SynchronizationContextTaskScheduler. – Chad

+0

LongRunning 작업이 UI 컨텍스트에서 실행되는 것과 똑같은 문제가 있었는데 TaskScheduler.Default가 해결되었습니다. 감사합니다! –

관련 문제