День триста двадцать первый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
Async/await были добавлены в .NET более 7 лет назад. За это время было выпущено множество улучшений в инфраструктуре, дополнительных языковых конструкций, API-интерфейсов. Однако один из аспектов async/await, который продолжает вызывать вопросы, - это
1. Что такое контекст синхронизации?
Документация по
В 99,9% случаев
В Windows Forms переопределённый метод
WPF имеет свой собственный производный от
В Windows RunTime (WinRT) переопределённый метод
Это выходит за рамки простого «запуска этого делегата в UI-потоке». Любой может реализовать
Преимуществом такого подхода в том, что он предоставляет единый API, который можно использовать для постановки в очередь делегата для обработки, как того пожелает создатель реализации, без необходимости знать детали этой реализации. Итак, если я пишу библиотеку, и я хочу асинхронно выполнить некоторую работу, а затем поставить делегат в очередь обратно в «контекст» исходного местоположения, мне просто нужно захватить
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
Async/await были добавлены в .NET более 7 лет назад. За это время было выпущено множество улучшений в инфраструктуре, дополнительных языковых конструкций, API-интерфейсов. Однако один из аспектов async/await, который продолжает вызывать вопросы, - это
ConfigureAwait
. В серии постов #AsyncAwaitFAQ мы ответим на несколько наиболее часто задаваемых вопросов про контекст синхронизации в async/await.1. Что такое контекст синхронизации?
Документация по
System.Threading.SynchronizationContext
утверждает, что он «обеспечивает базовую функциональность для распространения контекста синхронизации в различных моделях синхронизации». Не совсем понятное описание.В 99,9% случаев
SynchronizationContext
- это тип, который предоставляет виртуальный метод Post
, принимающий делегат для асинхронного выполнения (в SynchronizationContext
есть множество других виртуальных членов, но они гораздо реже используются). Метод Post
базового типа просто вызывает ThreadPool.QueueUserWorkItem
для асинхронного вызова предоставленного делегата. Однако производные типы переопределяют метод Post
, чтобы разрешить выполнение этого делегата в наиболее подходящем месте и в наиболее подходящее время.В Windows Forms переопределённый метод
Post
, создаёт эквивалент Control.BeginInvoke
. То есть любые вызовы метода Post
приведут к тому, что делегат будет вызван позже в потоке, связанном с этим элементом управления, т.е. в UI-потоке. Windows Forms полагается на обработку сообщений Win32 и имеет «цикл сообщений», работающий в UI-потоке, который просто ожидает поступления новых сообщений для обработки (движения и щелчки мыши, ввод с клавиатур, системные события, делегаты и т. д.). Таким образом, имея экземпляр SynchronizationContext
для UI-потока приложения Windows Forms, чтобы получить делегат для выполнения в UI-потоке, просто нужно передать его в Post
.WPF имеет свой собственный производный от
SynchronizationContext
тип с переопределённым Post
, который аналогично «маршализирует» делегат в UI-поток (через Dispatcher.BeginInvoke
), в данном случае управляемый диспетчером WPF, а не элементом управления Windows Forms.В Windows RunTime (WinRT) переопределённый метод
Post
, также ставит делегат в очередь UI-потока через CoreDispatcher
.Это выходит за рамки простого «запуска этого делегата в UI-потоке». Любой может реализовать
SynchronizationContext
с методом Post
, который делает всё что угодно. Преимуществом такого подхода в том, что он предоставляет единый API, который можно использовать для постановки в очередь делегата для обработки, как того пожелает создатель реализации, без необходимости знать детали этой реализации. Итак, если я пишу библиотеку, и я хочу асинхронно выполнить некоторую работу, а затем поставить делегат в очередь обратно в «контекст» исходного местоположения, мне просто нужно захватить
SynchronizationContext
, сохранить его, а затем, когда я закончу свою работу, вызвать Post
в этом контексте и передать делегат, который я хочу вызвать. Мне не нужно знать, что для Windows Forms я должен взять Control
и использовать его BeginInvoke
, или для WPF я должен взять Dispatcher
и использовать его BeginInvoke
, и т.п. Мне просто нужно взять текущий SynchronizationContext
и использовать его позже. Чтобы достичь этого, SynchronizationContext
предоставляет свойство Current
, так что для достижения вышеупомянутой цели можно написать такой код:public void DoWork(Action worker, Action completion)Платформа, которая хочет предоставить свой контекст в свойстве
{
SynchronizationContext sc = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(_ =>
{
try { worker(); }
finally { sc.Post(_ => completion(), null); }
});
}
Current
, использует метод SynchronizationContext.SetSynchronizationContext
.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
👍1
День триста двадцать второй. #ЧтоНовенького
Наткнулся (как обычно) в ютубе на следующее видео серии Visual Studio Toolbox. Новый фреймворк (привет Javascript) для .Net позволяет писать мультиплатформенный код на C# и XAML, который с минимальными доработками можно запускать на Windows (естественно), Android, iOS и даже запускать как веб-приложение на основе WebAssembly.
В решении создаётся сразу 5 проектов: с общим кодом, где будет 95% логики, формы XAML и файлы ресурсов, а также проекты под каждую платформу. Можно использовать как один интерфейс под все платформы, так и настроить его под родные элементы для каждой платформы. Более того, все приложения (включая веб) могут подстраиваться под тему, установленную на устройстве.
Конечно, пока есть некоторый скепсис по поводу того, что это (а тем более что-то более сложное) действительно будет безшовно и бескостыльно работать на всех платформах, но пока выглядит довольно впечатляюще.
Источник (на английском): https://www.youtube.com/watch?v=fyo2BI4rn0g
Наткнулся (как обычно) в ютубе на следующее видео серии Visual Studio Toolbox. Новый фреймворк (привет Javascript) для .Net позволяет писать мультиплатформенный код на C# и XAML, который с минимальными доработками можно запускать на Windows (естественно), Android, iOS и даже запускать как веб-приложение на основе WebAssembly.
В решении создаётся сразу 5 проектов: с общим кодом, где будет 95% логики, формы XAML и файлы ресурсов, а также проекты под каждую платформу. Можно использовать как один интерфейс под все платформы, так и настроить его под родные элементы для каждой платформы. Более того, все приложения (включая веб) могут подстраиваться под тему, установленную на устройстве.
Конечно, пока есть некоторый скепсис по поводу того, что это (а тем более что-то более сложное) действительно будет безшовно и бескостыльно работать на всех платформах, но пока выглядит довольно впечатляюще.
Источник (на английском): https://www.youtube.com/watch?v=fyo2BI4rn0g
За какой технологией, по-вашему, будущее?
Anonymous Poll
18%
Uno (или аналог) позволит легко создавать кроссплатформенные приложения
41%
WebAssembly захватит мир, а нативные приложения уйдут в прошлое
41%
Нативная разработка под каждую платформу так и останется приоритетной
День триста двадцать третий. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
2. Что такое планировщик задач?
Планировщик задач (
Когда задачи представляют из себя делегаты, которые могут быть поставлены в очередь и выполнены, они связываются с
Класс
Как и
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
2. Что такое планировщик задач?
Планировщик задач (
TaskScheduler
) представляет собой объект, выполняющий низкоуровневую работу с очередями в потоках. Он гарантирует, что работа в задаче рано или поздно будет выполнена.Когда задачи представляют из себя делегаты, которые могут быть поставлены в очередь и выполнены, они связываются с
System.Threading.Tasks.TaskScheduler
. Так же, как SynchronizationContext
предоставляет виртуальный метод Post
для постановки в очередь вызова делегата (с реализацией, позже вызывающей делегат с помощью типичных механизмов вызова делегата), TaskScheduler
предоставляет абстрактный метод QueueTask
(с реализацией, позже вызывающей эту задачу через метод ExecuteTask
).Класс
TaskScheduler
также служит точкой расширения для всей настраиваемой логики планирования. Планировщик по умолчанию, возвращаемый TaskScheduler.Default
, является пулом потоков, но можно унаследовать от TaskScheduler
и переопределить соответствующие методы для изменения того, когда и где вызывается Task
. Например, в базовых библиотеках есть тип System.Threading.Tasks.ConcurrentExclusiveSchedulerPair
. Экземпляр этого класса предоставляет два свойства типа TaskScheduler
: ExclusiveScheduler
и ConcurrentScheduler
. Задачи, запланированные через ConcurrentScheduler
, могут выполняться одновременно, но до определённого предела, назначенного ConcurrentExclusiveSchedulerPair
при его создании. Но никакие задачи ConcurrentScheduler
не будут выполняться, когда выполняется задача, запланированная для ExclusiveScheduler
, в котором только одна задача может выполняться в определённый момент времени. Таким образом, поведение ExclusiveScheduler
очень похоже на блокировку чтения/записи.Как и
SynchronizationContext
, TaskScheduler
также имеет свойство Current
, которое возвращает «текущий» TaskScheduler
. Однако, в отличие от SynchronizationContext
, здесь нет способа установки текущего планировщика. Вместо этого текущий планировщик - тот, который связан с текущей выполняющейся задачей, и он предоставляется системе как часть процесса запуска задачи. Так, например, следующая программа выведет «True», так как лямбда-выражение в StartNew
выполняется в ExclusiveScheduler
объекта ConcurrentExclusiveSchedulerPair
, и оно «увидит», что на этот планировщик установлен TaskScheduler.Current
:using System;
using System.Threading.Tasks;
class Program {
static void Main() {
var cesp = new ConcurrentExclusiveSchedulerPair();
Task.Factory.StartNew(() => {
Console.WriteLine(TaskScheduler.Current == cesp.ExclusiveScheduler);
}, default, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait();
}
}
TaskScheduler
также предоставляет статический метод FromCurrentSynchronizationContext
, возвращающий новый TaskScheduler
, связанный с SynchronizationContext.Current
. Все экземпляры Task
, поставленные в очередь возвращённого планировщика, будут выполнены с помощью вызова метода Post в этом контексте.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста двадцать четвёртый. #Оффтоп #97Вещей
97 Вещей, Которые Должен Знать Каждый Программист
15. Программируйте Осознанно
Попытка обосновать правильность кода вручную приводит к формальному доказательству, которое длиннее кода и с большей вероятностью содержит ошибки. Автоматизированные инструменты предпочтительнее, но не всегда возможны. Далее описано промежуточное решение: полуформальное определение правильности кода.
Основная идея в том, чтобы разделить весь рассматриваемый код на короткие секции - от одной строки, например, вызова функции, до блоков длиной менее 10 строк - и аргументировать их правильность. Аргументы должны быть достаточно убедительными, например, для коллеги-программиста, который будет играть роль вашего оппонента.
Блок должен быть выбран так, чтобы:
1. В каждой конечной точке состояние программы (счетчик программы и состояния всех «живых» объектов) удовлетворяло легко описываемому свойству.
2. Функциональность этого блока (преобразование состояния программы) можно легко описать как одну задачу.
Эти рекомендации упростят процесс обоснования правильности. Эти свойства конечных точек обобщают такие понятия, как пред- и постусловия для функций и инварианты для тел циклов и экземпляров классов. Стремление к тому, чтобы блоки были максимально независимы друг от друга, упрощает обоснование их правильности и необходимо для лёгкого изменения этих блоков.
Многие из хороших практик кодирования, которые хорошо известны (хотя, возможно, не так хорошо соблюдаются), облегчают обоснование правильности. Следовательно, просто попытавшись обосновать правильность своего кода, вы уже начинаете двигаться к хорошему стилю и структуре кода. Неудивительно, что большинство из этих практик могут быть проверены статическими анализаторами кода:
1. Избегайте операторов goto, так как они делают удаленные друг от друга блоки сильно связанными.
2. Избегайте изменяемых глобальных переменных, поскольку они делают все блоки, которые их используют, зависимыми.
3. Каждая переменная должна иметь наименьшую возможную область видимости. Например, локальный объект может быть объявлен непосредственно перед его первым использованием.
4. Делайте объекты неизменяемыми, когда это уместно.
5. Делайте код читаемым, используя отступы (как горизонтальные, так и вертикальные), например, выравнивая связанные структуры и используя пустую строку для разделения блоков.
6. Делайте код самодокументируемым, выбрав описательные (но относительно короткие) имена для объектов, типов, методов и т. д.
7. Если вам нужен вложенный блок, сделайте его методом.
8. Делайте свои функции короткими и сфокусированными на одной задаче. Старый лимит в 24 строки всё ещё актуален. Хотя размер экрана и разрешение изменились с 1960-х годов, способность человека воспринимать информацию осталась той же.
9. Функции должны иметь ограниченное число параметров (четыре - хорошая верхняя граница). Это не ограничивает количество данных, передаваемых функциям: группируйте связанные параметры в единый объект, что упростит понимание их связанности и согласованности.
10. В более общем смысле каждая единица кода, от блока до библиотеки, должна иметь минимальный интерфейс. Чем меньше связей блока с другими блоками, тем меньше требуется обосновывать правильность его работы. Это означает, что аксессоры (get) свойств объекта увеличивают его сложность. Не запрашивайте информацию у объекта для её обработки. Вместо этого попросите объект обработать уже имеющуюся у него информацию. Инкапсуляция – лучшее средство для минимализации интерфейсов.
11. Чтобы сохранить инварианты класса, не рекомендуется использовать мутаторы (set). Они, как правило, допускают нарушение инвариантов, управляющих состоянием объекта.
Помимо доказательства его правильности, в принципе любые обсуждения вашего кода помогут вам лучше его понимать. Обменивайтесь информацией, которую вы получаете, для пользы всех.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Yechiel Kimchi
97 Вещей, Которые Должен Знать Каждый Программист
15. Программируйте Осознанно
Попытка обосновать правильность кода вручную приводит к формальному доказательству, которое длиннее кода и с большей вероятностью содержит ошибки. Автоматизированные инструменты предпочтительнее, но не всегда возможны. Далее описано промежуточное решение: полуформальное определение правильности кода.
Основная идея в том, чтобы разделить весь рассматриваемый код на короткие секции - от одной строки, например, вызова функции, до блоков длиной менее 10 строк - и аргументировать их правильность. Аргументы должны быть достаточно убедительными, например, для коллеги-программиста, который будет играть роль вашего оппонента.
Блок должен быть выбран так, чтобы:
1. В каждой конечной точке состояние программы (счетчик программы и состояния всех «живых» объектов) удовлетворяло легко описываемому свойству.
2. Функциональность этого блока (преобразование состояния программы) можно легко описать как одну задачу.
Эти рекомендации упростят процесс обоснования правильности. Эти свойства конечных точек обобщают такие понятия, как пред- и постусловия для функций и инварианты для тел циклов и экземпляров классов. Стремление к тому, чтобы блоки были максимально независимы друг от друга, упрощает обоснование их правильности и необходимо для лёгкого изменения этих блоков.
Многие из хороших практик кодирования, которые хорошо известны (хотя, возможно, не так хорошо соблюдаются), облегчают обоснование правильности. Следовательно, просто попытавшись обосновать правильность своего кода, вы уже начинаете двигаться к хорошему стилю и структуре кода. Неудивительно, что большинство из этих практик могут быть проверены статическими анализаторами кода:
1. Избегайте операторов goto, так как они делают удаленные друг от друга блоки сильно связанными.
2. Избегайте изменяемых глобальных переменных, поскольку они делают все блоки, которые их используют, зависимыми.
3. Каждая переменная должна иметь наименьшую возможную область видимости. Например, локальный объект может быть объявлен непосредственно перед его первым использованием.
4. Делайте объекты неизменяемыми, когда это уместно.
5. Делайте код читаемым, используя отступы (как горизонтальные, так и вертикальные), например, выравнивая связанные структуры и используя пустую строку для разделения блоков.
6. Делайте код самодокументируемым, выбрав описательные (но относительно короткие) имена для объектов, типов, методов и т. д.
7. Если вам нужен вложенный блок, сделайте его методом.
8. Делайте свои функции короткими и сфокусированными на одной задаче. Старый лимит в 24 строки всё ещё актуален. Хотя размер экрана и разрешение изменились с 1960-х годов, способность человека воспринимать информацию осталась той же.
9. Функции должны иметь ограниченное число параметров (четыре - хорошая верхняя граница). Это не ограничивает количество данных, передаваемых функциям: группируйте связанные параметры в единый объект, что упростит понимание их связанности и согласованности.
10. В более общем смысле каждая единица кода, от блока до библиотеки, должна иметь минимальный интерфейс. Чем меньше связей блока с другими блоками, тем меньше требуется обосновывать правильность его работы. Это означает, что аксессоры (get) свойств объекта увеличивают его сложность. Не запрашивайте информацию у объекта для её обработки. Вместо этого попросите объект обработать уже имеющуюся у него информацию. Инкапсуляция – лучшее средство для минимализации интерфейсов.
11. Чтобы сохранить инварианты класса, не рекомендуется использовать мутаторы (set). Они, как правило, допускают нарушение инвариантов, управляющих состоянием объекта.
Помимо доказательства его правильности, в принципе любые обсуждения вашего кода помогут вам лучше его понимать. Обменивайтесь информацией, которую вы получаете, для пользы всех.
Источник: https://www.oreilly.com/library/view/97-things-every/9780596809515/
Автор оригинала – Yechiel Kimchi
День триста двадцать пятый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
3. Как SynchronizationContext и TaskScheduler связаны с await?
Представьте кнопку в приложении. Нажав на кнопку, мы хотим загрузить текст с веб-сайта и установить его в качестве текста кнопки. Доступ к кнопке возможен только из UI-потока, которому она принадлежит, поэтому, когда мы успешно загрузили новый текст и хотим установить его как текст кнопки, мы должны сделать это из потока, которому принадлежит элемент управления (из UI-потока). Если мы этого не сделаем, мы получим исключение:
Когда вы ожидаете чего-либо в C#, компилятор использует паттерн awaitable и просит «ожидаемый» (awaitable) объект (в данном случае
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
3. Как SynchronizationContext и TaskScheduler связаны с await?
Представьте кнопку в приложении. Нажав на кнопку, мы хотим загрузить текст с веб-сайта и установить его в качестве текста кнопки. Доступ к кнопке возможен только из UI-потока, которому она принадлежит, поэтому, когда мы успешно загрузили новый текст и хотим установить его как текст кнопки, мы должны сделать это из потока, которому принадлежит элемент управления (из UI-потока). Если мы этого не сделаем, мы получим исключение:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.' (Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку).Если бы мы писали это вручную, мы могли бы использовать
SynchronizationContext
, чтобы выполнить установку текста в исходном контексте, например через TaskScheduler
:private static readonly HttpClient сlient = new HttpClient();Либо можно напрямую использовать
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
сlient.GetStringAsync("https://example.com/gettext")
.ContinueWith(downloadTask => {
downloadBtn.Text = downloadTask.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
SynchronizationContext
: private void downloadBtn_Click(object sender, RoutedEventArgs e)Однако оба этих подхода явно используют функции обратного вызова. Вместо этого мы можем написать код естественным образом с помощью async/await:
{
SynchronizationContext sc = SynchronizationContext.Current;
сlient.GetStringAsync("https://example.com/gettext")
.ContinueWith(downloadTask => {
sc.Post(delegate {
downloadBtn.Text = downloadTask.Result;
}, null);
});
}
private async void downloadBtn_Click(object sender, RoutedEventArgs e)Этот код «просто работает», успешно устанавливая текст в UI-потоке, потому что, как и в случае с вручную реализованными версиями выше, ожидание задачи по умолчанию запоминает текущий контекст (
{
string text = await сlient.GetStringAsync("https://example.com/gettext");
downloadBtn.Text = text;
}
SynchronizationContext.Current
) или текущий планировщик (TaskScheduler.Current
). Когда вы ожидаете чего-либо в C#, компилятор использует паттерн awaitable и просит «ожидаемый» (awaitable) объект (в данном случае
Task
) предоставить ему объект «ожидателя» (awaiter) – в данном случае TaskAwaiter<string>
. Этот «ожидатель» отвечает за создание метода обратного вызова (часто называемого «продолжением»), который «вернёт выполнение в исходный код» после того, как задача завершит работу. И делает он это с захватом текущего контекста/планировщика. В упрощённом виде это выглядит так: object scheduler = SynchronizationContext.Current;Другими словами, сначала проверяется, установлен ли
if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
{
scheduler = TaskScheduler.Current;
}
SynchronizationContext
, и, если нет, установлен ли «нестандартный» TaskScheduler
. Если таким образом контекст/планировщик будет захвачен, он будет использован для выполнения метода обратного вызова соответственно в контексте или через планировщик. В противном случае чаще всего метод обратного вызова выполнится в том же контексте, что и ожидаемая задача.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
Друзья. Маленькое дополнение к сегодняшнему посту.
Мой канал участвует в конкурсе для авторов в сфере IT. Поэтому, если вам нравится, что я делаю, пожалуйста, поддержите меня.
https://tproger.ru/best-it-media-2019-user-voting/
(найти в списке можно просто поиском по "Net Разработчик")
Спасибо, что читаете.
Мой канал участвует в конкурсе для авторов в сфере IT. Поэтому, если вам нравится, что я делаю, пожалуйста, поддержите меня.
https://tproger.ru/best-it-media-2019-user-voting/
(найти в списке можно просто поиском по "Net Разработчик")
Спасибо, что читаете.
День триста двадцать шестой. #DesignPatterns
Паттерны проектирования
4. Паттерн «Итератор» (Iterator).
Практически любое приложение в той или иной мере работает с коллекциями данных. В некоторых случаях для обработки данных коллекции используется специфический интерфейс конкретных коллекций, но в большинстве случаев доступ к элементам осуществляется через специальный абстрактный слой — итератор.
Назначение: Итераторы предоставляют абстрактный интерфейс для доступа к содержимому составных объектов, не раскрывая клиентам их внутреннюю структуру. В результате получается чёткое разделение ответственности: клиенты получают возможность работать с разными коллекциями унифицированным образом, а классы коллекций становятся проще за счёт того, что ответственность за перебор элементов возлагается на отдельную сущность.
Классическая схема паттерна «Итератор» и реализация паттерна в .Net представлены на рисунке ниже:
-
-
перемещаться итератор. В .Net интерфейс имеет единственный метод
-
-
итератора для определённой коллекции.
Итераторы в .NET являются однонаправленными и только для чтения. То есть при изменении коллекции последующий вызов
Блоки итераторов
Процесс создания итераторов вручную довольно утомителен и включает управление состоянием и перемещением по элементам коллекции при вызове
С помощью блока итераторов можно создавать итераторы для своих коллекций, существующих коллекций или вообще для внешних ресурсов, таких как файлы. Для этого достаточно открыть файл в начале метода и возвращать прочитанные блоки данных с помощью
Итераторы, полученные с помощью блока итераторов, являются «ленивыми»: их тело исполняется не в момент вызова метода, а при переборе элементов с помощью метода
Кроме того, блоки итераторов можно использовать для создания генераторов последовательностей. Например, «бесконечного» генератора чисел Фибоначчи.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 4.
Паттерны проектирования
4. Паттерн «Итератор» (Iterator).
Практически любое приложение в той или иной мере работает с коллекциями данных. В некоторых случаях для обработки данных коллекции используется специфический интерфейс конкретных коллекций, но в большинстве случаев доступ к элементам осуществляется через специальный абстрактный слой — итератор.
Назначение: Итераторы предоставляют абстрактный интерфейс для доступа к содержимому составных объектов, не раскрывая клиентам их внутреннюю структуру. В результате получается чёткое разделение ответственности: клиенты получают возможность работать с разными коллекциями унифицированным образом, а классы коллекций становятся проще за счёт того, что ответственность за перебор элементов возлагается на отдельную сущность.
Классическая схема паттерна «Итератор» и реализация паттерна в .Net представлены на рисунке ниже:
-
Iterator
(IEnumerator<T>
) — определяет интерфейс итератора. В .Net имеет методы: MoveNext
(переход на следующий элемент, возвращает false
, если достигнут конец последовательности), Current
(возвращает текущий элемент) и Reset
(возвращает итератор к началу коллекции, реализуется не всегда).-
Aggregate
(IEnumerable<T>
) — коллекция, по которой можетперемещаться итератор. В .Net интерфейс имеет единственный метод
GetEnumerator
для получения экземпляра итератора.-
ConcreteAggregate1
(List<T>
) — конкретная реализация коллекции;-
ConcreteIterator
(List<T>.Enumerator
) — конкретная реализацияитератора для определённой коллекции.
Итераторы в .NET являются однонаправленными и только для чтения. То есть при изменении коллекции последующий вызов
MoveNext
итератора выбросит InvalidOperationException
. По той же причине нельзя изменять элементы коллекции в цикле foreach
. Цикл foreach
является универсальным инструментом для обработки коллекций/последовательностей. Компилятор преобразует его в (в упрощённом варианте) вызов метода MoveNext
и обращение к свойству Current
в цикле while
. Для поддержки цикла foreach
не обязательно наличие интерфейса IEnumerable
. Достаточно, чтобы класс коллекции содержал метод GetEnumerator
, который будет возвращать тип с методом MoveNext
, возвращающим bool
, и свойством Current
.Блоки итераторов
Процесс создания итераторов вручную довольно утомителен и включает управление состоянием и перемещением по элементам коллекции при вызове
MoveNext
. С помощью блока итераторов реализовать итератор существенно проще:public static IEnumerator<int> CustomArrayIterator(this int[] array) {Блок итераторов преобразуется компилятором языка C# в конечный автомат с несколькими состояниями, соответствующими начальному положению итератора (когда он указывает на –1-й элемент), конечному положению (когда итератор прошёл все элементы) и «среднему» положению, при котором он указывает на определённый элемент.
foreach (var n in array) { yield return n; }
}
С помощью блока итераторов можно создавать итераторы для своих коллекций, существующих коллекций или вообще для внешних ресурсов, таких как файлы. Для этого достаточно открыть файл в начале метода и возвращать прочитанные блоки данных с помощью
yield return
.Итераторы, полученные с помощью блока итераторов, являются «ленивыми»: их тело исполняется не в момент вызова метода, а при переборе элементов с помощью метода
MoveNext
. Кроме того, блоки итераторов можно использовать для создания генераторов последовательностей. Например, «бесконечного» генератора чисел Фибоначчи.
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 4.
День триста двадцать седьмой. #Оффтоп
Все выходные зависал на Codewars. Не то, чтобы я не знал раньше об этом сайте, просто как-то не доходили руки. А тут, как обычно, наткнулся в ютубе на видео парня, который работает в Гугле. Так вот, он рассказывал про то, как попал в Гугл, начав кодить за полгода до этого. Да, я тоже сначала не поверил, пока он не показал свой рейтинг на Codewars в почти 4500. Впрочем, про этого парня чуть позже напишу. А пока я решил проверить, насколько тяжело столько набрать.
Я уже писал про подобный сайт. Проблема в том, что там тебе сначала даются элементарные задачи, которые «щёлкаешь» и вроде даже входишь в раж, но в итоге их столько, что это быстро надоедает. Понятно, что начинающим это помогает «набить руку», но опытным после дцатой задачи на поиск элемента в массиве становится скучно. А поскольку прогресс построен на «пути от простого к сложному», то сложные задачи закрыты, пока не решены простые. Поэтому я заниматься там как-то быстро забросил. Да и Codewars тоже не спешил пробовать по этой же причине: пока доберёшься до интересных заданий, потратишь кучу времени.
Однако, всё оказалось совсем не так. При регистрации можно указать свой уровень: начинающий/джун/мидл/сениор, - и задачи выдаются соответственно уровню. То есть рейтинг то твой, конечно, сначала нулевой, и самый низкий 8kuy (ку? кю? куй?) уровень, короче, но задачи система даёт уже не элементарные, а чуть более сложные, и можно быстро добраться до более высокого уровня и более интересных задач. Кроме того, можно в принципе взять любую задачу любого уровня (хоть самого высокого 1го) и решать её.
Задачи построены по методу разработки через тестирование. Вам даётся задача и открытый список из 2-3 тестов, которые программа должна пройти, и собственно поле для написания кода. Хотя, без IntelliCode и автоформатирования писать там довольно грустно, поэтому писал в VS, а потом туда копировал. Да, испорчен, признаю. Что интересно, после этого можно попытаться отправить своё решение, и тогда код будет протестирован расширенным списком тестов, которые от вас скрыты. То есть вы не видите, что не так, просто факт, что какой-то тест выдал false вместо true. Это позволяет не подгонять решение под ответ, а действительно решать задачу.
За выходные набрал 185 баллов рейтинга и дошёл до 6 уровня. На задачи, как мне кажется, я тратил уйму времени. Дело в том, что в веб-разработке, которой я занимаюсь, довольно редко приходится развивать «мелкую моторику» (парсить строки, работать с байтами, писать низкоуровневые алгоритмы и т.п.). Зачастую это всё уже давно решено, написано в библиотеках, и ты просто вызываешь метод одной библиотеки, второй библиотеки, коллекции обрабатываешь LINQом и складываешь приложение, как паззл. А тут приходится попотеть: например, написать упрощённый интерпретатор для языка BrainFuck или разбить квадрат числа на сумму квадратов. И оказывается, что то, чем редко пользуешься, забывается очень быстро. Так что поставил себе цель периодически практиковаться там, а то даже стыдно перед собой.
Кстати, если вы ищете, какую бы программу написать себе для портфолио или просто для практики, Codewars, по-моему, идеальный вариант. Берёте задания 1-2го уровня – а это зачастую уже полноценные приложения - и вперёд. Языков программирования там, кстати, целая куча, поэтому подкачать скиллы можно почти в любом. А вот сам сайт, конечно, только на английском.
Все выходные зависал на Codewars. Не то, чтобы я не знал раньше об этом сайте, просто как-то не доходили руки. А тут, как обычно, наткнулся в ютубе на видео парня, который работает в Гугле. Так вот, он рассказывал про то, как попал в Гугл, начав кодить за полгода до этого. Да, я тоже сначала не поверил, пока он не показал свой рейтинг на Codewars в почти 4500. Впрочем, про этого парня чуть позже напишу. А пока я решил проверить, насколько тяжело столько набрать.
Я уже писал про подобный сайт. Проблема в том, что там тебе сначала даются элементарные задачи, которые «щёлкаешь» и вроде даже входишь в раж, но в итоге их столько, что это быстро надоедает. Понятно, что начинающим это помогает «набить руку», но опытным после дцатой задачи на поиск элемента в массиве становится скучно. А поскольку прогресс построен на «пути от простого к сложному», то сложные задачи закрыты, пока не решены простые. Поэтому я заниматься там как-то быстро забросил. Да и Codewars тоже не спешил пробовать по этой же причине: пока доберёшься до интересных заданий, потратишь кучу времени.
Однако, всё оказалось совсем не так. При регистрации можно указать свой уровень: начинающий/джун/мидл/сениор, - и задачи выдаются соответственно уровню. То есть рейтинг то твой, конечно, сначала нулевой, и самый низкий 8kuy (ку? кю? куй?) уровень, короче, но задачи система даёт уже не элементарные, а чуть более сложные, и можно быстро добраться до более высокого уровня и более интересных задач. Кроме того, можно в принципе взять любую задачу любого уровня (хоть самого высокого 1го) и решать её.
Задачи построены по методу разработки через тестирование. Вам даётся задача и открытый список из 2-3 тестов, которые программа должна пройти, и собственно поле для написания кода. Хотя, без IntelliCode и автоформатирования писать там довольно грустно, поэтому писал в VS, а потом туда копировал. Да, испорчен, признаю. Что интересно, после этого можно попытаться отправить своё решение, и тогда код будет протестирован расширенным списком тестов, которые от вас скрыты. То есть вы не видите, что не так, просто факт, что какой-то тест выдал false вместо true. Это позволяет не подгонять решение под ответ, а действительно решать задачу.
За выходные набрал 185 баллов рейтинга и дошёл до 6 уровня. На задачи, как мне кажется, я тратил уйму времени. Дело в том, что в веб-разработке, которой я занимаюсь, довольно редко приходится развивать «мелкую моторику» (парсить строки, работать с байтами, писать низкоуровневые алгоритмы и т.п.). Зачастую это всё уже давно решено, написано в библиотеках, и ты просто вызываешь метод одной библиотеки, второй библиотеки, коллекции обрабатываешь LINQом и складываешь приложение, как паззл. А тут приходится попотеть: например, написать упрощённый интерпретатор для языка BrainFuck или разбить квадрат числа на сумму квадратов. И оказывается, что то, чем редко пользуешься, забывается очень быстро. Так что поставил себе цель периодически практиковаться там, а то даже стыдно перед собой.
Кстати, если вы ищете, какую бы программу написать себе для портфолио или просто для практики, Codewars, по-моему, идеальный вариант. Берёте задания 1-2го уровня – а это зачастую уже полноценные приложения - и вперёд. Языков программирования там, кстати, целая куча, поэтому подкачать скиллы можно почти в любом. А вот сам сайт, конечно, только на английском.
День триста двадцать восьмой. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
4. Что делает ConfigureAwait(false)?
Метод
Зачем использовать ConfigureAwait(false)?
Этот метод используется, чтобы избежать принудительного вызова метода обратного вызова в исходном контексте или планировщике. Это даёт несколько преимуществ:
1. Улучшает производительность. Требуется дополнительная работа, чтобы поставить в очередь метод обратного вызова вместо того, чтобы просто вызывать его. Кроме того, некоторые оптимизации времени выполнения, не могут быть использованы. Иногда даже проверка текущих контекста и планировщика (что включает в себя доступ к статическим потокам) могут добавить измеримые накладные расходы. Если код после
2. Позволяет избежать взаимных блокировок. Взаимная блокировка чаще всего возникает при использовании кода, блокирующего текущий поток (при вызове
Зачем использовать ConfigureAwait(true)?
Поскольку захват контекста происходит по умолчанию, то
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
4. Что делает ConfigureAwait(false)?
Метод
ConfigureAwait
, принимающий параметр continueOnCapturedContext
(«продолжитьВЗахваченномКонтексте
»), возвращает структуру ConfiguredTaskAwaitable
, которая содержит специальным образом настроенный «ожидатель» (awaiter). Поскольку await
может использоваться с любым типом, соответствующим паттерну awaitable (не только с Task
напрямую), возврат другого типа вместо задачи позволяет изменить логику захвата контекста/планировщика (см. предыдущий пост) на что-то вроде этого: object scheduler = null;Другими словами, получая значение
if (continueOnCapturedContext)
{
// захват контекста/планировщика
}
false
, объект не захватывает текущий контекст/планировщик, даже если они существуют.Зачем использовать ConfigureAwait(false)?
Этот метод используется, чтобы избежать принудительного вызова метода обратного вызова в исходном контексте или планировщике. Это даёт несколько преимуществ:
1. Улучшает производительность. Требуется дополнительная работа, чтобы поставить в очередь метод обратного вызова вместо того, чтобы просто вызывать его. Кроме того, некоторые оптимизации времени выполнения, не могут быть использованы. Иногда даже проверка текущих контекста и планировщика (что включает в себя доступ к статическим потокам) могут добавить измеримые накладные расходы. Если код после
await
фактически не требует запуска в исходном контексте, использование ConfigureAwait(false)
поможет избежать всех этих затрат.2. Позволяет избежать взаимных блокировок. Взаимная блокировка чаще всего возникает при использовании кода, блокирующего текущий поток (при вызове
.Wait()
, .Result
или .GetAwaiter().GetResult()
на задаче, внутри которой используется await). Если в текущем контексте явно или неявно ограничено число параллельных операций, либо если код блокирует UI-поток, получается ситуация, когда контекст/поток заблокирован в ожидании завершения асинхронной задачи, а метод обратного вызова этой задачи ставится в очередь для вызова на заблокированном контексте/потоке и поэтому не может быть выполнен (взаимная блокировка). Такая ситуация может возникать, когда ресурсы ограничены любым способом и так или иначе оказываются заняты. Если задача использует ConfigureAwait(false)
, метод обратного вызова не ставится в очередь в исходный контекст, что позволяет избежать сценариев взаимоблокировки.Зачем использовать ConfigureAwait(true)?
Поскольку захват контекста происходит по умолчанию, то
ConfigureAwait(true)
практически никогда не используется, за исключением случаев, когда вы хотите показать, что вы намеренно не используете ConfigureAwait(false)
(например, чтобы отключить предупреждения статического анализа кода). Метод ConfigureAwait
принимает логическое значение, поскольку существуют некоторые нишевые ситуации, в которых вы хотите передать переменную для управления конфигурацией. Но в 99% случаев используется жестко закодированное значение false
. Поэтому вызовы ConfigureAwait(true)
можно безболезненно удалять.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста двадцать девятый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
5. Когда использовать ConfigureAwait(false)?
Это зависит от того, реализуете ли вы код уровня приложения или код библиотеки общего назначения. При написании приложений обычно предпочтительно поведение по умолчанию. Если модель приложения или среда (например, Windows Forms, WPF, ASP.NET Core и т. д.) публикует собственный
И наоборот, библиотеки общего назначения являются «универсальными» отчасти потому, что их не волнует среда, в которой они используются. Вы можете использовать их в веб-приложении, в клиентском приложении или в тесте, это не имеет значения, поскольку код библиотеки не зависит от модели приложения, в которой он может быть использован. Эта независимость также означает, что код не собирается делать что-то, что должно взаимодействовать с моделью приложения строго определённым образом. Например, он не будет получать доступ к элементам управления UI, потому что библиотека общего назначения ничего не знает о них. Поскольку отпадает необходимость запускать код в какой-либо конкретной среде, мы можем избежать принудительного возврата продолжений/обратных вызовов в исходный контекст через
В любых правилах, конечно, могут быть исключения. Например, стоит обдумать решение для библиотек общего назначения с API, принимающим делегаты. В таких случаях пользователь библиотеки передаёт для вызова в библиотеке код приложения. Рассмотрим, например, асинхронную версию LINQ метода
Гарантирует ли ConfigureAwait(false), что обратный вызов не будет выполняться в исходном контексте?
Нет. Он гарантирует, что он не будет поставлен в очередь в исходный контекст… но это не означает, что код после
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
5. Когда использовать ConfigureAwait(false)?
Это зависит от того, реализуете ли вы код уровня приложения или код библиотеки общего назначения. При написании приложений обычно предпочтительно поведение по умолчанию. Если модель приложения или среда (например, Windows Forms, WPF, ASP.NET Core и т. д.) публикует собственный
SynchronizationContext
, почти наверняка для этого есть веская причина: контекст позволяет коду правильно взаимодействовать с моделью приложения/средой. Поэтому, если вы пишете обработчик событий в приложении Windows Forms, модульный тест в xunit, код в контроллере ASP.NET MVC, то независимо от того, опубликовала ли модель приложения SynchronizationContext
, вы предпочли бы использовать его, если он существует. Поэтому по умолчанию используется ConfigureAwait(true)
. Вы просто используете await
, и обратные вызовы/продолжения отправляются обратно в исходный контекст, как и должны. Общее правило: если вы пишете код уровня приложения, не используйте ConfigureAwait(false)
. Если вернуться к обработчику события Click в примере с кнопкой, то кодdownloadBtn.Text = text;должен быть выполнен в исходном контексте. В этом примере вызов асинхронного кода с
ConfigureAwait(false)приводил бы к ошибке. То же самое относится и к коду в классическом приложении ASP.NET, зависящем от
HttpContext.Current
. Использование ConfigureAwait(false)
, а затем попытка использования HttpContext.Current
скорее всего приведёт к проблемам.И наоборот, библиотеки общего назначения являются «универсальными» отчасти потому, что их не волнует среда, в которой они используются. Вы можете использовать их в веб-приложении, в клиентском приложении или в тесте, это не имеет значения, поскольку код библиотеки не зависит от модели приложения, в которой он может быть использован. Эта независимость также означает, что код не собирается делать что-то, что должно взаимодействовать с моделью приложения строго определённым образом. Например, он не будет получать доступ к элементам управления UI, потому что библиотека общего назначения ничего не знает о них. Поскольку отпадает необходимость запускать код в какой-либо конкретной среде, мы можем избежать принудительного возврата продолжений/обратных вызовов в исходный контекст через
ConfigureAwait(false)
, получая преимущества как в производительности, так и в надёжности. Здесь общее правило такое: если вы пишете код библиотеки общего назначения, используйте ConfigureAwait(false)
. Вот почему, например, практически любое (за некоторыми исключениями) ожидание в библиотеках среды выполнения .NET Core использует ConfigureAwait(false)
.В любых правилах, конечно, могут быть исключения. Например, стоит обдумать решение для библиотек общего назначения с API, принимающим делегаты. В таких случаях пользователь библиотеки передаёт для вызова в библиотеке код приложения. Рассмотрим, например, асинхронную версию LINQ метода
Where
:public static async IAsyncEnumerable<T> WhereAsync(this IAsyncEnumerable<T> source, Func<T, bool> predicate);Нужно ли здесь вызывать predicate в исходном
SynchronizationContext
вызывающей стороны? Это зависит от реализации WhereAsync
. По этой причине можно отказаться от использования ConfigureAwait(false)
.Гарантирует ли ConfigureAwait(false), что обратный вызов не будет выполняться в исходном контексте?
Нет. Он гарантирует, что он не будет поставлен в очередь в исходный контекст… но это не означает, что код после
await task.ConfigureAwait(false)не будет исполнен в исходном контексте. Это связано с тем, что код, расположенный после уже завершённых задач, просто продолжает исполняться синхронно, а не ставится в продолжение/обратный вызов. Таким образом, если вы ожидаете задачу, которая уже выполнена к тому времени, когда вызывается await, независимо от того, использовали ли вы
ConfigureAwait(false)
, последующий код сразу продолжит выполняться в текущем потоке, в текущем контексте.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста тридцатый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
6. Можно ли использовать ConfigureAwait(false) только в первом ожидании методе, но не в остальных?
В общем случае нет. Смотрите предыдущий пост. Если ожидание
Исключением является ситуация, когда вы точно знаете, что первое ожидание всегда завершается асинхронно, а обратному вызову задачи не нужен текущий контекст и планировщик задач. Например,
Можно ли использовать Task.Run, чтобы избежать использования ConfigureAwait(false)?
Да. В следующем коде
Также заметим, что при этом подходе необходимо создавать/ставить в очередь дополнительный объект задачи. Это может иметь или не иметь значения для вашего приложения или библиотеки в зависимости от того, критична ли для вас производительность.
Кроме того, имейте в виду, что такие уловки могут вызвать больше проблем, чем принесут пользы, и иметь другие непредвиденные последствия. Например, инструменты статического анализа (например, анализаторы Roslyn) были написаны для отметки ожиданий, которые не используют
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
6. Можно ли использовать ConfigureAwait(false) только в первом ожидании методе, но не в остальных?
В общем случае нет. Смотрите предыдущий пост. Если ожидание
await task.ConfigureAwait(false)включает в себя задачу, которая уже завершена (что на самом деле случается очень часто), то
ConfigureAwait(false)
будет бессмысленным, и поток продолжит выполнять код метода дальше в том же контексте.Исключением является ситуация, когда вы точно знаете, что первое ожидание всегда завершается асинхронно, а обратному вызову задачи не нужен текущий контекст и планировщик задач. Например,
CryptoStream
в библиотеках .NET Runtime хочет, чтобы его потенциально требовательный к вычислительным ресурсам код не выполнялся как часть синхронного вызова вызывающей стороны, поэтому он использует настраиваемый ожидатель, чтобы весь код после первого ожидания выполнялся в потоке из пула. Однако даже в этом случае можно заметить, что в следующем ожидании всё равно используется ConfigureAwait(false)
. Технически это не является необходимым, но это делает обзор кода намного проще, так как при чтении этого кода не нужно ломать голову, почему в остальных случаях нет вызова ConfigureAwait(false)
.Можно ли использовать Task.Run, чтобы избежать использования ConfigureAwait(false)?
Да. В следующем коде
Task.Run(async delegate {использование
await SomethingAsync();
});
ConfigureAwait(false)
для SomethingAsync
не имеет смысла, т.к. делегат, переданный в Task.Run
, будет выполняться в потоке из пула без учёта контекста. Кроме того, Task.Run
неявно использует TaskScheduler.Default
, что означает, что запрос TaskScheduler.Current
внутри делегата также возвратит Default
. Это означает, что ожидание будет демонстрировать одинаковое поведение независимо от того, использовался ли ConfigureAwait(false)
. Кроме того, нет никаких гарантий относительно того, что может делать код внутри этого делегата. Если у вас есть код:Task.Run(async delegateтогда код внутри
{
SynchronizationContext.SetSynchronizationContext(new SomeCoolSyncCtx());
await SomethingAsync();
});
SomethingAsync
на самом деле увидит в SynchronizationContext.Current
экземпляр SomeCoolSyncCtx
, и как это ожидание, так и любые ненастроенные ожидания внутри SomethingAsync
будут отправлять обратные вызовы в этот контекст. Поэтому, чтобы использовать этот подход, вам необходимо понимать, что может и что не может делать код, который вы ставите в очередь, и могут ли его действия что-то испортить.Также заметим, что при этом подходе необходимо создавать/ставить в очередь дополнительный объект задачи. Это может иметь или не иметь значения для вашего приложения или библиотеки в зависимости от того, критична ли для вас производительность.
Кроме того, имейте в виду, что такие уловки могут вызвать больше проблем, чем принесут пользы, и иметь другие непредвиденные последствия. Например, инструменты статического анализа (например, анализаторы Roslyn) были написаны для отметки ожиданий, которые не используют
ConfigureAwait(false)
, предупреждением CA2007. Если вы включили такой анализатор, но затем применили хитрость, подобную этой, просто чтобы избежать использования ConfigureAwait
, есть большая вероятность, что анализатор всё равно пометит его предупреждением.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста тридцать первый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
7. Нужно ли использовать ConfigureAwait(false) при вызове GetAwaiter().GetResult()?
Нет.
Говорят, что ConfigureAwait(false) больше не требуется в .NET Core. Это правда?
Нет. Он необходим при работе в .NET Core по тем же причинам, что и при работе в .NET Framework. В этом отношении ничего не изменилось.
Однако изменилось поведение некоторых сред в части публикации собственного контекста. Например, если ASP.NET в .NET Framework имеет свой собственный
Но это не означает, что пользовательский
Можно ли использовать ConfigureAwait в await foreach для IAsyncEnumerable?
Да.
Можно ли использовать ConfigureAwait в await using для IAsyncDisposable?
Да, хотя есть небольшой нюанс. Как и в случае
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
7. Нужно ли использовать ConfigureAwait(false) при вызове GetAwaiter().GetResult()?
Нет.
ConfigureAwait
влияет только на обратные вызовы. В частности, паттерн awaiter требует, чтобы ожидатели предоставили свойство IsCompleted
, метод GetResult
и метод OnCompleted
. ConfigureAwait влияет только на поведение OnCompleted
, поэтому, если вы просто напрямую вызываете метод GetResult()
разницы в поведении не будет. Поэтому можно заменитьtask.ConfigureAwait(false).GetAwaiter().GetResult();на
task.GetAwaiter().GetResult();если, конечно, вы действительно хотите блокировать текущий поток.
Говорят, что ConfigureAwait(false) больше не требуется в .NET Core. Это правда?
Нет. Он необходим при работе в .NET Core по тем же причинам, что и при работе в .NET Framework. В этом отношении ничего не изменилось.
Однако изменилось поведение некоторых сред в части публикации собственного контекста. Например, если ASP.NET в .NET Framework имеет свой собственный
SynchronizationContext
, то в ASP.NET Core нет. Это означает, что код, работающий в приложении ASP.NET Core по умолчанию, не увидит пользовательский SynchronizationContext
, что исключает необходимость вызова ConfigureAwait(false)
в такой среде.Но это не означает, что пользовательский
SynchronizationContext
или TaskScheduler
никогда не будут предоставлены. Если какой-либо пользовательский код (или код библиотеки), используемый вашим приложением, задаёт пользовательский контекст, то даже в ASP.NET Core ваши вызовы await могут захватывать контекст или планировщик, что может повлечь необходимость использования ConfigureAwait(false)
.Можно ли использовать ConfigureAwait в await foreach для IAsyncEnumerable?
Да.
await foreach
работает с шаблоном, поэтому он может использоваться для перечисления не только IAsyncEnumerable<T>
, но и всего, что соответствует шаблону. Библиотеки .NET Runtime включают метод расширения ConfigureAwait
для IAsyncEnumerable<T>
, который возвращает сконфигурированную структуру, соответствующую шаблону. Когда компилятор генерирует вызовы методов MoveNextAsync
и DisposeAsync
перечислителя, структура выполняет ожидание желаемым способом.Можно ли использовать ConfigureAwait в await using для IAsyncDisposable?
Да, хотя есть небольшой нюанс. Как и в случае
IAsyncEnumerable<T>
, библиотеки .NET Runtime предоставляют метод расширения ConfigureAwait
для IAsyncDisposable
. И await using
будет успешно работать с ним, поскольку он реализует соответствующий шаблон (предоставляет метод DisposeAsync
):await using (var c = new MyAsyncDisposableClass().ConfigureAwait(false)) {…}Проблема здесь в том, что тип c теперь не
MyAsyncDisposableClass
, а System.Runtime.CompilerServices.ConfiguredAsyncDisposable
, возвращаемый из метода расширения ConfigureAwait
. Чтобы обойти это, вам нужно написать одну дополнительную строку:var c = new MyAsyncDisposableClass();Это расширяет область действия
await using (c.ConfigureAwait(false)){…}
c
, и, если это критично, можно обернуть всё это в фигурные скобки.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста тридцать второй. #ЗадачиНаСобеседовании
Встречаю много вопросов на тему, чего ждать на собеседовании, к чему готовиться, что нужно знать и т.п. Поскольку лично я работаю в одной компании уже 12 лет, то считать себя экспертом по собесам не могу. Поэтому лучше сошлюсь на хорошее видео на эту тему от АйТиБороды.
А в этом посте хотел бы привести одну из практических задач. Не знаю, как у нас, а на собеседованиях в заграничных «ИТ-гигантах» умение решать задачи ценится больше, чем просто знание технологий. Возможно, это и правильно. Как я упоминал ранее, я нашёл канал одного парня, его зовут Клемент Михалеску, который занимается интервью кандидатов в Гугле. Так вот, в одном видео (не спешите смотреть, чтобы не заспойлерить) мне понравилась задача, которую предлагают решить кандидату на собеседовании.
Дана коллекция точек на плоскости с координатами (x, y). См. рисунок ниже (он немного кривоват, но суть, думаю, понятна). Для простоты допустим, что все координаты целочисленные. Нужно написать функцию, которая будет вычислять количество прямоугольников, образованных этими точками. Очевидные правила:
- прямоугольник должен иметь 4 точки в углах,
- ненулевые длину и ширину,
- прямоугольники, образованные четырьмя одинаковыми точками A,B,E,D (см. рисунок) считаются как один (то есть ABED = BEDA = EDBA и т.п. – все считать за 1).
Итак, что мне понравилось в описании процесса интервью.
Во-первых, интервью длится около часа, и никто не ждёт от вас, что вы тут же напишете идеально правильный, компилирующийся и работающий код. Тем более, что на интервью даётся листок бумаги (документ Google Docs в видео), а не IDE. Интервьюеру интересен ход ваших мыслей. То есть вам даётся задание, вы начинаете рассуждать вслух, возможно записывать какой-то псевдокод. Хороший интервьюер (как в видео) будет рассуждать вместе с вами и задавать наводящие вопросы или корректировать, если вы полезете не в ту степь. Точно так же, как впоследствии вы будете работать и решать задачи в команде.
Во-вторых, сначала вам дают упрощённый вариант задачи. В данном случае допустим, что нужно посчитать прямоугольники, стороны которых параллельны осям координат. То есть пока мы не будем считать прямоугольник BDHF. Кстати, в видео «кандидат» блестяще с этим справляется, можете посмотреть решение до 20й минуты.
Кроме собственно решения интервьюер попросил пройти его по шагам, чтобы убедиться в правильности и понять ход мыслей самому, а также оценить «стоимость» решения как по времени, так и по занимаемой памяти. Вот это тоже нужно уметь делать.
Ну а дальше, если интервьюер действительно хочет оценить силу кандидата, то даст ему попробовать решить и усложнённый вариант. Собственно, Клемент поясняет далее во врезке, что от кандидата не ждут, что он сразу, прямо во время интервью, решит усложнённый вариант (чаще всего это не реально). Интервьюеру интереснее посмотреть на мыслительный процесс кандидата и то, как он справляется с действительно сложными задачами.
Интересно, что я практически уверен, что решение упрощённого варианта в видео правильное, а вот до решения усложнённого варианта, как мне показалось, они так и не дошли.
Итак, теперь нам нужно посчитать все возможные прямоугольники.
У меня есть теория решения, в которой я более-менее уверен, но пока не проверял. Приглашаю всех предлагать варианты решения в чате.
Встречаю много вопросов на тему, чего ждать на собеседовании, к чему готовиться, что нужно знать и т.п. Поскольку лично я работаю в одной компании уже 12 лет, то считать себя экспертом по собесам не могу. Поэтому лучше сошлюсь на хорошее видео на эту тему от АйТиБороды.
А в этом посте хотел бы привести одну из практических задач. Не знаю, как у нас, а на собеседованиях в заграничных «ИТ-гигантах» умение решать задачи ценится больше, чем просто знание технологий. Возможно, это и правильно. Как я упоминал ранее, я нашёл канал одного парня, его зовут Клемент Михалеску, который занимается интервью кандидатов в Гугле. Так вот, в одном видео (не спешите смотреть, чтобы не заспойлерить) мне понравилась задача, которую предлагают решить кандидату на собеседовании.
Дана коллекция точек на плоскости с координатами (x, y). См. рисунок ниже (он немного кривоват, но суть, думаю, понятна). Для простоты допустим, что все координаты целочисленные. Нужно написать функцию, которая будет вычислять количество прямоугольников, образованных этими точками. Очевидные правила:
- прямоугольник должен иметь 4 точки в углах,
- ненулевые длину и ширину,
- прямоугольники, образованные четырьмя одинаковыми точками A,B,E,D (см. рисунок) считаются как один (то есть ABED = BEDA = EDBA и т.п. – все считать за 1).
Итак, что мне понравилось в описании процесса интервью.
Во-первых, интервью длится около часа, и никто не ждёт от вас, что вы тут же напишете идеально правильный, компилирующийся и работающий код. Тем более, что на интервью даётся листок бумаги (документ Google Docs в видео), а не IDE. Интервьюеру интересен ход ваших мыслей. То есть вам даётся задание, вы начинаете рассуждать вслух, возможно записывать какой-то псевдокод. Хороший интервьюер (как в видео) будет рассуждать вместе с вами и задавать наводящие вопросы или корректировать, если вы полезете не в ту степь. Точно так же, как впоследствии вы будете работать и решать задачи в команде.
Во-вторых, сначала вам дают упрощённый вариант задачи. В данном случае допустим, что нужно посчитать прямоугольники, стороны которых параллельны осям координат. То есть пока мы не будем считать прямоугольник BDHF. Кстати, в видео «кандидат» блестяще с этим справляется, можете посмотреть решение до 20й минуты.
Кроме собственно решения интервьюер попросил пройти его по шагам, чтобы убедиться в правильности и понять ход мыслей самому, а также оценить «стоимость» решения как по времени, так и по занимаемой памяти. Вот это тоже нужно уметь делать.
Ну а дальше, если интервьюер действительно хочет оценить силу кандидата, то даст ему попробовать решить и усложнённый вариант. Собственно, Клемент поясняет далее во врезке, что от кандидата не ждут, что он сразу, прямо во время интервью, решит усложнённый вариант (чаще всего это не реально). Интервьюеру интереснее посмотреть на мыслительный процесс кандидата и то, как он справляется с действительно сложными задачами.
Интересно, что я практически уверен, что решение упрощённого варианта в видео правильное, а вот до решения усложнённого варианта, как мне показалось, они так и не дошли.
Итак, теперь нам нужно посчитать все возможные прямоугольники.
У меня есть теория решения, в которой я более-менее уверен, но пока не проверял. Приглашаю всех предлагать варианты решения в чате.
👍1
Интересны ли вам подобные задачи на канале?
Anonymous Poll
43%
Да
48%
Да, делать пост с объяснением решения позже
9%
Нет
День триста тридцать третий. #DesignPatterns
Паттерны проектирования
5. Паттерн «Наблюдатель» (Observer)
Существует два способа общения между двумя программными элементами.
Назначение: определяет зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.
Причины использования:
При проектировании некоторого класса у разработчика всегда есть несколько вариантов реализации. Класс
Классическая диаграмма приведена на рисунке ниже:
-
-
-
-
На платформе .NET практически невозможно встретить классическую реализацию паттерна «Наблюдатель». Существует несколько вариантов реализации:
1. С помощью делегатов (методов обратного вызова).
Самая простая форма наблюдателя. Для этого достаточно, чтобы класс потребовал делегат в аргументах конструктора и уведомлял вызывающий код с его помощью. Это позволяет гарантировать наличие наблюдателя, а также наблюдаемый объект может не только уведомлять об изменении своего состояния, но и требовать от делегата некоторого результата.
2. С помощью событий (events).
События представляют собой умную оболочку над делегатами, которая позволяет клиентам лишь подписываться на события или отказываться от подписки, а владельцу события — инициировать событие для уведомления всех подписчиков. Разница с первым вариантом в том, что интерфейс наблюдаемого объекта позволяет подписаться на событие любому числу подписчиков. При этом нет гарантии, что эти подписчики вообще будут. Подробнее о событиях
3. С помощью специализированных интерфейсов-наблюдателей.
В некоторых случаях, когда имеется несколько событий или делегатов, их удобно объединить в одном интерфейсе. Данный вариант очень похож на классическую версию паттерна «Наблюдатель», хотя обычно наблюдатель является единственным.
4. С помощью интерфейсов IObserver/IObservable.
Все перечисленные ранее варианты реализации паттерна «Наблюдатель» содержат одно ограничение: они плохо объединяются для получения более высокоуровневого поведения (not composable). Над событиями или делегатами невозможно выполнять операции, которые можно выполнять над последовательностями. С 4-й версии в .NET Framework появилась пара интерфейсов
Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 5.
Паттерны проектирования
5. Паттерн «Наблюдатель» (Observer)
Существует два способа общения между двумя программными элементами.
Компонент 1
может обратиться к Компоненту 2
для получения каких-то данных или выполнения некоторой операции, либо Компонент 2
может уведомить Компонент 1
о некотором событии. Первая модель взаимодействия называется pull-моделью, а вторая — push-моделью. В мире ООП push-модель реализуется с помощью паттерна «Наблюдатель».Назначение: определяет зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.
Причины использования:
При проектировании некоторого класса у разработчика всегда есть несколько вариантов реализации. Класс
А
может знать о существовании класса Б
и уведомлять его о произошедшем событии. Или же класс А
может быть наблюдаемым и уведомлять о некотором событии всех заинтересованных подписчиков. Использование наблюдателей уменьшает связанность между классами/модулями и упрощает повторное использование. Благодаря слабой связанности наблюдаемый объект и наблюдатели могут располагаться на разных уровнях абстракции или слоях приложения. Например, слой пользовательского интерфейса знает о модели, но модель не должна ничего знать о пользовательском интерфейсе.Классическая диаграмма приведена на рисунке ниже:
-
Observer
— определяет интерфейс наблюдателя;-
Subject
(наблюдаемый объект) — определяет методы подключения и отключения наблюдателей;-
ConcreteObserver
— реализует интерфейс наблюдателя;-
ConcreteSubject
— конкретный тип наблюдаемого объекта.На платформе .NET практически невозможно встретить классическую реализацию паттерна «Наблюдатель». Существует несколько вариантов реализации:
1. С помощью делегатов (методов обратного вызова).
Самая простая форма наблюдателя. Для этого достаточно, чтобы класс потребовал делегат в аргументах конструктора и уведомлял вызывающий код с его помощью. Это позволяет гарантировать наличие наблюдателя, а также наблюдаемый объект может не только уведомлять об изменении своего состояния, но и требовать от делегата некоторого результата.
2. С помощью событий (events).
События представляют собой умную оболочку над делегатами, которая позволяет клиентам лишь подписываться на события или отказываться от подписки, а владельцу события — инициировать событие для уведомления всех подписчиков. Разница с первым вариантом в том, что интерфейс наблюдаемого объекта позволяет подписаться на событие любому числу подписчиков. При этом нет гарантии, что эти подписчики вообще будут. Подробнее о событиях
3. С помощью специализированных интерфейсов-наблюдателей.
В некоторых случаях, когда имеется несколько событий или делегатов, их удобно объединить в одном интерфейсе. Данный вариант очень похож на классическую версию паттерна «Наблюдатель», хотя обычно наблюдатель является единственным.
4. С помощью интерфейсов IObserver/IObservable.
Все перечисленные ранее варианты реализации паттерна «Наблюдатель» содержат одно ограничение: они плохо объединяются для получения более высокоуровневого поведения (not composable). Над событиями или делегатами невозможно выполнять операции, которые можно выполнять над последовательностями. С 4-й версии в .NET Framework появилась пара интерфейсов
IObserver
/IObservable
с набором методов расширений, известных под названием реактивных расширений (Rx, Reactive Extensions). Интерфейс IObservable
моделирует реактивные последовательности и позволяет работать с наблюдаемыми последовательностями через привычный LINQ-синтаксис. Он предполагает, что однородные события будут периодически повторяться. Наблюдаемый объект может уведомить о новом событии (OnNext
), о том, что в процессе события произошла ошибка (OnError
), или о том, что цепочка событий завершена (OnComplete
). Пример: поток сетевых сообщений от клиента или сервера, координаты устройства и т.п.Источник: Тепляков С. "Паттерны проектирования на платформе .NET." — СПб.: Питер, 2015. Глава 5.
День триста тридцать четвёртый. #ЗадачиНаСобеседовании
Ответ на задачу
Дана коллекция точек на плоскости с координатами (x, y). Для простоты допустим, что все координаты целочисленные. Нужно написать функцию, которая будет вычислять количество прямоугольников, которые образуют эти точки.
Как я уже говорил, у меня есть теория решения, в которой я более-менее уверен, но пока не проверял.
Итак, пусть у нас есть коллекция точек Point { int x; int y; }. Мы проходим по коллекции и для каждой точки добавляем в коллекцию вектор, начинающийся в этой точке и заканчивающийся в любой точке, которая выше этой точки (координата y больше координаты y текущей точки – y2 > y1), либо на том же уровне по высоте, но правее (x2 > x1, если y2 = y1). Таким образом для точки A (см. рисунок ниже) мы добавляем векторы AB, AC, AD, но не добавляем вектор AE (он будет учтён для точки E, как EA). Кроме того, для каждого вектора мы считаем его «направление» (x2 – x1, y2 – y1). Например, для точек A(0,0) и B(-1,4), направление вектора AB = (-1,4), а для AD = (11,7).
Таким образом получим коллекцию векторов
double
Таким образом считаем количество для всех векторов и делим общее количество на 2, поскольку мы посчитаем прямоугольник ABCD дважды для векторов AB и AC).
По поводу стоимости: если у нас N точек, тогда в худшем случае у будет N(N-1)/2 векторов, то есть O(N^2) времени на создание векторов и O(N^4) на обход.
Как-то так. Если у вас есть вопросы или замечания, добро пожаловать в чат.
Ответ на задачу
Дана коллекция точек на плоскости с координатами (x, y). Для простоты допустим, что все координаты целочисленные. Нужно написать функцию, которая будет вычислять количество прямоугольников, которые образуют эти точки.
Как я уже говорил, у меня есть теория решения, в которой я более-менее уверен, но пока не проверял.
Итак, пусть у нас есть коллекция точек Point { int x; int y; }. Мы проходим по коллекции и для каждой точки добавляем в коллекцию вектор, начинающийся в этой точке и заканчивающийся в любой точке, которая выше этой точки (координата y больше координаты y текущей точки – y2 > y1), либо на том же уровне по высоте, но правее (x2 > x1, если y2 = y1). Таким образом для точки A (см. рисунок ниже) мы добавляем векторы AB, AC, AD, но не добавляем вектор AE (он будет учтён для точки E, как EA). Кроме того, для каждого вектора мы считаем его «направление» (x2 – x1, y2 – y1). Например, для точек A(0,0) и B(-1,4), направление вектора AB = (-1,4), а для AD = (11,7).
Таким образом получим коллекцию векторов
struct Vector
{
Point A { get; set; }
Point B { get; set; }
(int x, int y) Direction
{
get
{
return (B.X - A.X, B.Y - A.Y);
}
}
}
Теперь для каждого вектора из набора ищем вектор с равным ему направлением и начальной точкой, выбранной по тому же правилу (выше начальной точки текущего вектора или на том же уровне, но правее). Например, для вектора AB таким будет вектор CD (но для вектора CD мы не найдём вектора AB, т.к. A ниже C). Мы получили две параллельные и равные стороны (поскольку направления равны). Остался ещё один момент. У прямоугольников есть свойство, что их диагонали равны. Поэтому сравниваем длины диагоналей (не используем квадратный корень из формулы расстояния, чтобы не заморачиваться с округлением):double
AD = Math.Pow(vCD.A.X - vAB.B.X, 2) + Math.Pow(vCD.A.Y - vAB.B.Y, 2);
double BC = Math.Pow(vAB.B.X - vCD.A.X, 2) + Math.Pow(vAB.B.Y - vCD.A.Y, 2);
Если диагонали AD и BC равны, у нас есть прямоугольник.Таким образом считаем количество для всех векторов и делим общее количество на 2, поскольку мы посчитаем прямоугольник ABCD дважды для векторов AB и AC).
По поводу стоимости: если у нас N точек, тогда в худшем случае у будет N(N-1)/2 векторов, то есть O(N^2) времени на создание векторов и O(N^4) на обход.
Как-то так. Если у вас есть вопросы или замечания, добро пожаловать в чат.