2

我感兴趣的是为什么我们需要调用 InvokeOnMainThread 而这将是 TaskScheduler.FromCurrentSynchronizationContext() 的主要意图和责任?

我在 Monotouch 中为 iPhone 应用程序使用 TPL 来执行一些后台任务并通过记者类更新 UI。但似乎 TaskScheduler.FromCurrentSynchronizationContext() 没有像您期望的那样与 UI 线程同步。此时,我设法通过使用 InvokeOnMainThread 使其工作(但仍然感觉不对),如Xamarin 网站上的线程主题所述。

我还在 BugZilla 上发现了一个报告的(类似)错误,似乎已经解决了。还有另一个关于在 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 中进行了报告!!!


进展:我还没有弄清楚这个..有人吗?

4

2 回答 2

1

我认为问题在于您通过执行从 ProgressReporter 类中检索任务调度程序TaskScheduler.FromCurrentSynchronizationContext()

您应该将任务计划程序传递给 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 线程上完成:

TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
ProgressReporter progressReporter = new ProgressReporter(uiScheduler);
于 2012-03-20T20:22:57.167 回答
0

你用的是什么版本的MonoTouch,输出的是什么:TaskScheduler.FromCurrentSynchronizationContext().GetType().ToString()。如果上下文已正确注册,它应该是 UIKitSynchronizationContext 类型的类。如果这是正确类型的上下文,您是否可以通过直接调用上下文上的 Post 和 Send 方法来进行快速测试,以查看它们是否最终在正确的线程上执行。您需要启动几个线程池线程来测试它是否正常工作,但它应该相当简单。

于 2012-03-21T01:59:28.947 回答