2

私が興味を持っているのは、InvokeOnMainThreadを呼び出す必要があるのですが、これはTaskScheduler.FromCurrentSynchronizationContext()の主な目的と責任ですか?なぜTaskScheduler.FromCurrentSynchronizationContextはMonotouchで同期しませんか?

iPhoneアプリのMonotouchでTPLを使用して、いくつかのバックグラウンドタスクを実行し、レポータークラスを介してUIを更新しています。しかし、TaskScheduler.FromCurrentSynchronizationContext()はUIスレッドと期待通りに同期していないようです。現時点では、XamarinのサイトのThreadingトピックで説明されているように、InvokeOnMainThreadを使用することで問題なく動作します(ただし、間違っていると感じています)。

また、BugZillaで報告された(類似した)bugが解決されているように見え、MonoTouchで背景スレッドを使用する好ましい方法についてもう1つの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にタスクスケジューラを渡して、代わりにそのいずれかを使用する必要があります

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スレッドから取られたタスクスケジューラを渡すことによって、あなたはどんな報告が行われていることを確認しています

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

ありがとうございました。でも、これを試してみましたが、うまくいきませんでした。コンソール出力で同じ結果が得られ、UIに進捗状況が表示されません。私の質問で述べたように、 'ContinueWith()'は 'InvokeOnMainThread()'を呼び出す必要なく動作するようです。 –

+0

私はWinFormsプロジェクトを使用してWindowsでのみ試したことを認めなければなりません。 Windowsでは、コンソールプロジェクトは 'TaskScheduler.FromCurrentSynchronizationContext()'を許可しません。例外がスローされます: '現在のSynchronizationContextはTaskSchedulerとして使用できません。' –

+0

@RemcoKoedoot、このテクニックを使用する最新のコードを(質問の編集として)表示できますか? –

0

使用しているMonoTouchでのどのバージョンと何の出力です::TaskScheduler.FromCurrentSynchronizationContext().GetType().ToString()はUIスレッド上。コンテキストが正しく登録されている場合は、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に投稿していないのは困惑していますスタックに沿ったパラメータとして使用します。 –