2

내가 관심을 가지고있는 것은 InvokeOnMainThread를 호출해야하는 이유입니다. TaskScheduler.FromCurrentSynchronizationContext()?의 주요 의도와 책임은 무엇입니까?TaskScheduler.FromCurrentSynchronizationContext가 Monotouch에서 동기화되지 않는 이유는 무엇입니까?

저는 iPhone 앱을위한 Monotouch에서 TPL을 사용하여 백그라운드 작업을 수행하고 리포터 클래스를 통해 UI를 업데이트합니다. 하지만 TaskScheduler.FromCurrentSynchronizationContext()는 예상대로 UI 스레드와 동기화되지 않는 것으로 보입니다. 지금은 Xamarin 사이트의 Threading 주제에 설명 된대로 InvokeOnMainThread를 사용하여 문제를 해결할 수있었습니다.

또한 BugZilla에서보고 된 (유사한) bug이 해결 된 것으로 확인되었고 Monochay에 백그라운드 스레드를 사용하는 기본 방법에 대해 다른 threading question이 발견되었습니다.

다음은 내 질문을 설명하고 동작을 보여주는 코드 단편입니다.

private CancellationTokenSource cancellationTokenSource; 

    private void StartBackgroundTask() 
    { 
     this.cancellationTokenSource = new CancellationTokenSource(); 
     var cancellationToken = this.cancellationTokenSource.Token; 
     var progressReporter = new ProgressReporter(); 

     int n = 100; 
     var uiThreadId = Thread.CurrentThread.ManagedThreadId; 
     Console.WriteLine ("Start in thread " + uiThreadId); 

     var task = Task.Factory.StartNew (() => 
     { 
      for (int i = 0; i != n; ++i) { 

       Console.WriteLine ("Work in thread " + Thread.CurrentThread.ManagedThreadId); 

       Thread.Sleep (30); 

       progressReporter.ReportProgress (() => 
       { 
        Console.WriteLine ("Reporting in thread {0} (should be {1})", 
         Thread.CurrentThread.ManagedThreadId, 
         uiThreadId); 

        this.progressBar.Progress = (float)(i + 1)/n; 
        this.progressLabel.Text = this.progressBar.Progress.ToString(); 

       }); 
      } 

      return 42; // Just a mock result 
     }, cancellationToken); 

     progressReporter.RegisterContinuation (task,() => 
     { 
      Console.WriteLine ("Result in thread {0} (should be {1})", 
       Thread.CurrentThread.ManagedThreadId, 
       uiThreadId); 

      this.progressBar.Progress = (float)1; 
      this.progressLabel.Text = string.Empty; 

      Util.DisplayMessage ("Result","Background task result: " + task.Result); 

     }); 
    } 

그리고 기자 클래스가 이러한 방법
public void ReportProgress(Action action) 
    { 
     this.ReportProgressAsync(action).Wait(); 
    } 
    public Task ReportProgressAsync(Action action) 
    { 
     return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 
    } 
    public Task RegisterContinuation(Task task, Action action) 
    { 
     return task.ContinueWith(() => action(), CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 
    } 
    public Task RegisterContinuation<TResult>(Task<TResult> task, Action action) 
    { 
     return task.ContinueWith(() => action(), CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 
    } 

응용 프로그램 출력 창에서 결과

은 다음과 같습니다

Start in thread 1 
Work in thread 6 
Reporting in thread 6 (should be 1) 
Work in thread 6 
Reporting in thread 6 (should be 1) 
... 
Result in thread 1 (should be 1) 

당신이 '스레드 6 일이'괜찮 볼 수 있듯이 . 스레드 6에 대한보고도 잘못되었습니다. 재미있는 부분은 RegisterContinuation이 스레드 1에서보고를 수행한다는 것입니다.


진행 : 아직이 문제를 파악하지 못했습니다. 누구입니까?

답변

1

내가 문제는 TaskScheduler.FromCurrentSynchronizationContext()을 수행하여 ProgressReporter 클래스에서 작업 스케줄러를 가져 오는 것입니다.

당신은 ProgressReporter에 작업 스케줄러를 통과하고 대신에 하나를 사용한다 : 진행 기자로 UI 스레드에서 가져온 작업 스케줄러를 전달하여

public class ProgressReporter 
{ 
    private readonly TaskScheduler taskScheduler; 

    public ProgressReporter(TaskScheduler taskScheduler) 
    { 
     this.taskScheduler = taskScheduler; 
    } 

    public Task RegisterContinuation(Task task, Action action) 
    { 
     return task.ContinueWith(n => action(), CancellationToken.None, 
      TaskContinuationOptions.None, taskScheduler); 
    } 

    // Remaining members... 
} 

을, 당신은 어떤보고가 이루어집니다 확신 UI 스레드에서 : 당신이 사용하고 무엇을하는 MonoTouch 어떤 버전의

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 
ProgressReporter progressReporter = new ProgressReporter(uiScheduler); 
+0

감사합니다 Sandor하지만이 시도하고 작동하지 않았다. 콘솔 결과와 동일한 결과를 제공하며 UI에 진행 상황이 표시되지 않습니다. 내 질문에 언급 한 바와 같이 'ContinueWith()'InvokeOnMainThread() '호출 할 필요없이 작동하는 것 같습니다. –

+0

WinForms 프로젝트를 사용하여 Windows에서만이 작업을 시도했음을 인정해야합니다. Windows에서 콘솔 프로젝트는'TaskScheduler.FromCurrentSynchronizationContext()'를 허용하지 않습니다. 예외가 발생합니다 : '현재 SynchronizationContext는 TaskScheduler로 사용할 수 없습니다.' –

+0

@RemcoKoedoot,이 기술을 사용하는 업데이트 된 코드를 질문에 편집하여 보여줄 수 있습니까? –

0

이의 출력 : TaskScheduler.FromCurrentSynchronizationContext()가 .GetType()로 .toString(). 컨텍스트가 올바르게 등록 된 경우 UIKitSynchronizationContext 유형의 클래스 여야합니다. 이것이 올바른 유형의 컨텍스트 인 경우 컨텍스트에서 Post 및 Send 메서드를 직접 호출하여 올바른 스레드에서 실행되는지 확인하여 빠른 테스트를 수행 할 수 있습니다. 스레드 풀이 제대로 작동하는지 테스트하기 위해 약간의 스레드 풀 스레드를 만들어야하지만 상당히 간단해야합니다.

+0

안녕 Alan, MonoDevelop 2.8.6.5 및 MonoTouch 5.2.10.1332177242를 사용하고 있습니다. 나는 네가 제안한 것을 아직 확인하지 못했지만 분명히 할 것이다. 나는 결과를 게시 할 것이다. –

+0

이 호출은 System.Threading.Tasks.SynchronizationContextScheduler 유형을 반환합니다. 하지만 SynchronizationContext.Current를 실행하면 UIKitSynchronizationContext 유형이됩니다. –

+0

Post와 함께 테스트를 수행하고 대기중인 스레드에서 SynchronizationContext.Current를 전송했지만 결과는 이전과 동일합니다. SynchronizationContext.Current를 For 루프 밖으로 가져 와서 해당 컨텍스트의 대기중인 스레드 내에서 Post를 호출하면 예상대로 작동했습니다 ... 그래서 TaskScheduler가 MainThread에 게시하지 않는 이유는 무엇입니까? 스택을 따라 매개 변수로. –

관련 문제