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

相关问题