2

我感興趣的是爲什麼我們需要調用InvokeOnMainThread,而這將是TaskScheduler.FromCurrentSynchronizationContext()的主要意圖和責任。爲什麼不在Monotouch中同步TaskScheduler.FromCurrentSynchronizationContext?

我正在Monotouch中爲iPhone應用程序使用TPL來完成一些後臺任務並通過記者級更新UI。但是,似乎TaskScheduler.FromCurrentSynchronizationContext()並不像您所期望的那樣與UI線程同步。此時,我通過使用Xamarin站點中Threading主題描述的InvokeOnMainThread,設法使其工作(但仍然感覺錯誤)。

我還發現在BugZilla上報告(類似)bug,似乎已解決..和另一個threading question關於在MonoTouch中使用後臺線程的首選方法。

以下是用於說明我的問題和顯示行爲的代碼片段。

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線程進入進步記者拍攝的任務調度程序,你確定的任何報告完成在UI線程上:您使用的,什麼什麼版本的MonoTouch的

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

謝謝桑德爾,但我試過這個,它沒有工作。它在控制檯輸出中給出相同的結果,UI中沒有顯示進度。正如我的問題所述,'ContinueWith()'似乎無需調用'InvokeOnMainThread()'即可工作。 – 2012-03-21 00:50:27

+0

我不得不承認,我僅在Windows上使用WinForms項目嘗試了此操作。在Windows上,控制檯項目甚至不允許使用TaskScheduler.FromCurrentSynchronizationContext(),它會導致引發異常:「當前的SynchronizationContext不能用作TaskScheduler。」 – 2012-03-21 08:51:12

+0

@RemcoKoedoot,你能否展示你使用這種技術的更新代碼(作爲對問題的編輯)? – 2013-05-10 21:27:22

0

是輸出:TaskScheduler.FromCurrentSynchronizationContext().GetType()的ToString()。如果上下文已經正確註冊,它應該是一個類型爲UIKitSynchronizationContext的類。如果這是一個正確類型的上下文,你可以通過直接調用上下文的Post和Send方法來做一個快速測試,看看它們是否最終在正確的線程上執行。你需要啓動幾個線程池線程來測試它是否工作正常,但它應該相當簡單。

+0

嗨Alan,我正在使用MonoDevelop 2.8.6.5和MonoTouch 5.2.10.1332177242。我沒有找到時間來檢查你的建議,但我一定會這樣做。我會公佈結果。 – 2012-03-22 09:42:26

+0

該調用返回類型System.Threading.Tasks.SynchronizationContextScheduler。但是當我運行SynchronizationContext.Current時,它的類型是UIKitSynchronizationContext。 – 2012-03-25 00:34:53

+0

我用排隊線程中的Post和Send on SynchronizationContext.Current做了測試,但結果與之前一樣。當我將SynchronizationContext.Current從For循環中取出並從該上下文的排隊線程中調用Post時,它按預期工作......所以我仍然困惑爲什麼TaskScheduler不在MainThread上發佈,即使我給它作爲沿着堆棧的參數。 – 2012-03-25 00:45:34

相關問題