Почему "SwitchTo" был удален из Async CTP/Release?

Я попытался использовать метод SwitchTo сегодня, чтобы переключиться на поток GUI, и обнаружил, что пример, который я снял с него, не работает, просто потому, что метода там нет.

Затем я нашел этот рекламный блок здесь:

Причина, по которой мы избавились от этого, была потому, что это было так опасно. Альтернативой является сбор кода внутри TaskEx.Run...

Мой вопрос просто: почему это было опасно? С какими конкретными опасностями это может привести?

Обратите внимание, что я прочитал остальную часть этого сообщения, поэтому понимаю, что здесь существуют технические ограничения. Мой вопрос по-прежнему, если я знаю об этом, почему это опасно?

Я рассматриваю возможность переопределения вспомогательных методов, чтобы дать мне указанную функциональность, но если есть что-то принципиально нарушенное, кроме того, что кто-то решил, что это опасно, я бы этого не сделал.

В частности, очень наивно, вот как я мог бы рассмотреть возможность применения необходимых методов:

public static class ContextSwitcher
{
    public static ThreadPoolContextSwitcher SwitchToThreadPool()
    {
        return new ThreadPoolContextSwitcher();
    }

    public static SynchronizationContextSwitcher SwitchTo(this SynchronizationContext synchronizationContext)
    {
        return new SynchronizationContextSwitcher(synchronizationContext);
    }
}

public class SynchronizationContextSwitcher : INotifyCompletion
{
    private readonly SynchronizationContext _SynchronizationContext;

    public SynchronizationContextSwitcher(SynchronizationContext synchronizationContext)
    {
        _SynchronizationContext = synchronizationContext;
    }

    public SynchronizationContextSwitcher GetAwaiter()
    {
        return this;
    }

    public bool IsCompleted
    {
        get
        {
            return false;
        }
    }

    public void OnCompleted(Action action)
    {
        _SynchronizationContext.Post(_ => action(), null);
    }

    public void GetResult()
    {
    }
}

public class ThreadPoolContextSwitcher : INotifyCompletion
{
    public ThreadPoolContextSwitcher GetAwaiter()
    {
        return this;
    }

    public bool IsCompleted
    {
        get
        {
            return false;
        }
    }

    public void OnCompleted(Action action)
    {
        ThreadPool.QueueUserWorkItem(_ => action(), null);
    }

    public void GetResult()
    {
    }
}

Это позволит мне написать такой код:

public async void Test()
{
    await ContextSwitcher.SwitchToThreadPool(); // ensure we're not bogging down the UI thread
    // do some heavy processing
    await _UIContext.SwitchTo(); // presumably saved from the main thread
    // update UI with new data
}

Ответ 1

У Стивена Туба есть дополнительная информация о рассуждениях в этот поток.

Подводя итог, это не очень хорошая идея по двум причинам:

  • Он продвигает неструктурированный код. Если у вас есть "тяжелая обработка", которую вам нужно сделать, ее следует разместить в Task.Run. Еще лучше отделите свою бизнес-логику от логики пользовательского интерфейса.
  • Обработка ошибок и (некоторые) продолжения выполняются в неизвестном контексте. Блоки catch/finally в Test должны обрабатывать запуск в пуле потоков или контексте пользовательского интерфейса (и если они работают в контексте пула потоков, они не могут использовать SwitchTo для перехода в пользовательский интерфейс контекст). Кроме того, до тех пор, пока вы await возвратили Task, вы должны быть в порядке (await исправит контекст продолжения, если необходимо), но если у вас есть явные ContinueWith продолжения, которые используют ExecuteSynchronously, тогда они будут имеют ту же проблему, что и блоки catch/finally.

Короче говоря, код более чист и более предсказуем без SwitchTo.

Ответ 2

ConfigureAwait на самом деле более опасен, чем SwitchTo. Ментальное отслеживание текущего контекста и последнего вызова SwitchTo не сложнее, чем отслеживание, когда последняя была назначена переменной. С другой стороны, ConfigureAwait переключает контекст тогда и только тогда, когда вызов фактически выполнялся асинхронно. Если задача уже завершена, контекст сохраняется. У вас нет контроля над этим.