Если вы пишете библиотеки, то в большинстве случаев следует вызывать ConfigureAwait(false) везде, где используется await. Это связано с тем, что продолжение через контекст синхронизации может быть затратным, а в некоторых случаях способно привести к взаимоблокировке. Единственным исключением являются случаи, когда вы делаете что-то, что определенно требует сохранения контекста синхронизации, или же вы точно знаете, что ваша библиотека всегда будет использоваться только в средах приложений, которые не устанавливают контекст синхронизации. (Например, приложения ASP.NET Core не используют контексты синхронизации, поэтому обычно не имеет значения, вызываете ли вы в них ConfigureAwait(false).)
Листинг 17.15 показывает минимальную рабочую реализацию шаблона await. Она чрезмерно упрощена, так как всегда завершается синхронно, поэтому ее метод OnCompleted ничего не делает. Фактически при использовании шаблона await этот метод никогда не будет вызываться, поэтому у меня он вызывает исключение. И хотя этот пример крайне прост, он служит прекрасной иллюстрацией того, что делает await
Различие между этими интерфейсами и их соответствующими методами заключается в том, что первый требует, чтобы ожидающий передавал текущий контекст выполнения целевому методу, а последний — нет.
Вам придется реализовать INotifyCompletion, а еще есть дополнительный интерфейс, который тоже рекомендуется по возможности реализовать. Он называется ICriticalNotifyCompletion. Они выполняют похожие задачи: каждый определяет один метод (OnCompleted и UnsafeOnCompleted соответственно), который принимает единственный делегат Action, и ожидающий должен вызывать этот делегат после завершения операции.
Компилятору также нужен способ получить результат после завершения работы, поэтому у ожидающего должен быть метод GetResult. Его возвращаемый тип определяет тип результата операции — это будет тип выражения await.