Почему TaskScheduler.FromCurrentSynchronizationContext не синхронизируется в Monotouch?

Что меня интересует, так это то, почему нам нужно вызывать InvokeOnMainThread, в то время как это будет основным намерением и ответственностью TaskScheduler.FromCurrentSynchronizationContext()?.

Я использую TPL в Monotouch для приложения для iPhone, чтобы выполнить некоторые фоновые задачи и обновить пользовательский интерфейс с помощью класса репортера. Но кажется, что TaskScheduler.FromCurrentSynchronizationContext () не синхронизируется с потоком пользовательского интерфейса так, как вы ожидаете. В это время мне удалось заставить его работать (но все еще чувствует себя не так), используя InvokeOnMainThread, как описано в теме Threading на сайте 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!!!


ПРОГРЕСС: Я до сих пор не понял этого.. Кто-нибудь?

2 ответа

Я думаю, проблема в том, что вы извлекаете планировщик задач из класса 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...
}

Передав планировщик задач, взятый из потока пользовательского интерфейса, в программу отчетов о ходе работы, вы уверены, что все отчеты создаются в потоке пользовательского интерфейса:

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

Какую версию MonoTouch вы используете и каков результат: TaskScheduler.FromCurrentSynchronizationContext ().GetType ().ToString (). Это должен быть класс типа UIKitSynchronizationContext, если контекст был правильно зарегистрирован. Если это контекст правильного типа, не могли бы вы сделать быстрый тест, напрямую вызвав методы Post и Send для этого контекста, чтобы убедиться, что они выполняются в правильном потоке. Вам нужно будет раскрутить несколько потоков потоков, чтобы проверить, что он работает правильно, но это должно быть достаточно просто.

Другие вопросы по тегам