Почему TaskScheduler.FromCurrentSynchronizationContext не синхронизируется в Monotouch? - PullRequest
2 голосов
/ 19 марта 2012

Меня интересует то, почему нам нужно вызывать 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 ]

1 голос
/ 21 марта 2012

Думаю, проблема в том, что вы извлекаете планировщик задач из класса 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);
0 голосов
/ 21 марта 2012

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

...