Почему "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 переключает контекст тогда и только тогда, когда вызов фактически выполнялся асинхронно. Если задача уже завершена, контекст сохраняется. У вас нет контроля над этим.