【问题标题】:Why doesn't TaskScheduler.FromCurrentSynchronizationContext synchronize in Monotouch?为什么 TaskScheduler.FromCurrentSynchronizationContext 在 Monotouch 中不同步?
【发布时间】:2012-03-19 16:07:11
【问题描述】:

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

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

我还在 BugZilla 上发现了一个报告的(类似的)bug,它似乎已经解决了。还有另一个 threading question 关于在 MonoTouch 中使用后台线程的首选方式。

下面是代码 sn-p 来说明我的问题并显示行为。

    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 中进行报告!!!


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

【问题讨论】:

    标签: c# multithreading xamarin.ios task-parallel-library synchronizationcontext


    【解决方案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 线程上完成的:

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

    【讨论】:

    • 谢谢 Sandor,但我试过了,但没有用。它在控制台输出中给出了相同的结果,并且 UI 中没有显示任何进度。正如我的问题中提到的,“ContinueWith()”似乎无需调用“InvokeOnMainThread()”即可工作。
    • 我不得不承认我只在 Windows 上使用 WinForms 项目进行了尝试。在 Windows 上,控制台项目甚至不允许 TaskScheduler.FromCurrentSynchronizationContext(),它会导致抛出异常:“当前 SynchronizationContext 可能无法用作 TaskScheduler。”
    • @RemcoKoedoot,您能否展示使用此技术的更新代码(作为问题的编辑)?
    【解决方案2】:

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

    【讨论】:

    • 嗨艾伦,我正在使用 MonoDevelop 2.8.6.5 和 MonoTouch 5.2.10.1332177242。我还没有时间检查你的建议,但我一定会做的。我会发布结果。
    • 该调用返回 System.Threading.Tasks.SynchronizationContextScheduler 类型。但是当我运行 SynchronizationContext.Current 时,它的类型是 UIKitSynchronizationContext。
    • 我在 SynchronizationContext.Current 上使用来自排队线程的 Post 和 Send 进行了测试,但结果与以前相同。当我将 SynchronizationContext.Current 从 For 循环中取出并从该 Context 上的排队线程中调用 Post 时,它按预期工作......所以我仍然很困惑为什么 TaskScheduler 没有在 MainThread 上发布,即使我给它作为堆栈中的参数。
    猜你喜欢
    • 1970-01-01
    • 2016-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-09
    相关资源
    最近更新 更多