Почему всегда рекомендуется писать ConfigureAwait (false) в каждой строке с await и действительно ли это мне нужно? - PullRequest
2 голосов
/ 21 июня 2020

Вопрос не в том, что делает ConfigureAwait. А скорее, почему буквально везде я вижу что-то вроде

Как правило, да. ConfigureAwait (false) следует использовать для каждого ожидания, если только метод не нуждается в его контексте.

То есть они предлагают мне написать

await Method1().ConfigureAwait(false);
await Method2().ConfigureAwait(false);
// Do something else
// ...
await Method3().ConfigureAwait(false);
await Method4().ConfigureAwait(false);

Но в таком случае не будет яснее, просто сбросив контекст в самом начале только один раз, например,

await Task.Yield().ConfigureAwait(false);

Это гарантирует, что приведенный ниже код будет выполнен без контекста syn c, не так ли?

Т.е. прочтите, что запись ConfigureAwait один раз может не сработать, если метод вернется немедленно. И для меня очевидное решение выглядит как вызов ConfigureAwait (false) для чего-то, что точно не возвращается немедленно, что такое Task.Yield, верно?

Также, как я знаю, Task.Yield не содержит ConfigureAwait больше (не знаю почему, поскольку я знаю, что раньше он имелся), но, глядя на код Task.Yield, довольно легко написать свой собственный метод, который бы ничего не делал, кроме вызова продолжения с пустым синхросигналом. c context.

И мне кажется намного проще читать и особенно писать, когда вы пишете один раз

await TaskUtility.ResetSyncContext();

, чем писать ConfigureAwait в каждой строке.

Будет ли это работать (Task.Yield (). ConfigureAwait (false) или аналогичный настраиваемый метод) или я что-то пропущу?

1 Ответ

5 голосов
/ 22 июня 2020

Как правило, да. ConfigureAwait (false) следует использовать для каждого ожидания, если только метод не нуждается в его контексте.

Я часто видел этот совет здесь, на Stack Overflow, и это даже то, что говорит Стивен Клири (Microsoft MVP) в его статье Asyn c и Await :

Хорошее практическое правило - использовать ConfigureAwait (false), если вы не знаете, что вам нужен контекст.

Стивен определенно знает свое дело, и я согласен с тем, что совет технически точен, но я всегда думал, что это плохой совет по двум причинам:

  1. новичкам и
  2. Риск обслуживания

Во-первых, это плохой совет для новичков, потому что контекст синхронизации - сложная тема. Если вы начинаете изучать async / await с того, что вам говорят, что «ConfigureAwait(false) следует использовать для каждого await, если методу не нужен его контекст», но вы даже не знаете, что такое «контекст» и что он означает "нуждаться в нем", тогда вы не знаете, когда вы не должны его использовать, так что в итоге вы всегда используете его. Это означает, что вы можете столкнуться с ошибками, которые будет очень трудно выяснить, если вы не узнаете об этом. Да, вам действительно нужна была эта «контекстная» вещь, и эта волшебная вещь «ConfigureAwait» заставила вас ее потерять. Вы можете потратить часы, пытаясь понять это.

Для приложений любого типа, я считаю, совет действительно должен быть противоположным: не используйте ConfigureAwait вообще, если вы не знаете, что он делает, и вы определили, что вам абсолютно не нужен контекст после этой строки.

Однако определение того, что вам не нужен контекст, может быть простым или довольно сложным в зависимости от того, какие методы вызываются после. Но даже тогда - и это вторая причина, по которой я не согласен с этим советом - то, что вам не нужен контекст после этой строки прямо сейчас , не означает, что какой-то код не будет добавлен позже, будет использовать контекст. Вам придется надеяться, что тот, кто вносит это изменение, знает, что делает ConfigureAwait(false), видит это и удаляет. Использование ConfigureAwait(false) повсюду создает риск обслуживания.

Это то, что другой Стивен, Стивен Туб (сотрудник Microsoft), рекомендует в ConfigureAwait FAQ под заголовком «Когда следует использовать ConfigureAwait. (false)? ":

При написании приложений обычно требуется поведение по умолчанию (вот почему это поведение по умолчанию). ... Это приводит к общему руководству: если вы пишете код уровня приложения, не используйте ConfigureAwait(false)

В моем собственном коде приложения я не Не пытаюсь понять, где я могу и не могу это использовать. Я просто игнорирую существование ConfigureAwait. Конечно, может повысить производительность, если использовать его там, где это возможно, но я действительно сомневаюсь, что это будет заметной разницей для любого человека, даже если это измерить таймером. Я не верю, что окупаемость инвестиций будет положительной.

Единственное исключение - когда вы пишете библиотеки, как указывает Стивен Туб в своей статье:

если вы пишете код библиотеки общего назначения, используйте ConfigureAwait(false)

Это по двум причинам:

  1. Библиотека ничего не знает о контекст приложения, в котором он используется, поэтому он все равно не может использовать контекст, и
  2. Если человек, использующий библиотеку, решит синхронно ждать кода вашей асинхронной библиотеки, это может вызвать тупик, который они не могут изменить, потому что они не могут изменить ваш код. (в идеале они не должны этого делать, но это может случиться)

Чтобы ответить на другой вопрос в вашем вопросе: не всегда достаточно использовать ConfigureAwait(false) на первом await, а не на остальное. Используйте его на каждые await в коде вашей библиотеки. Статья Стивена Туба под заголовком «Можно ли использовать ConfigureAwait (false) только для первого ожидания в моем методе, а не для остальных?» частично говорит:

Если await task.ConfigureAwait(false) включает в себя задачу, которая уже завершена к моменту ее ожидания (что на самом деле невероятно распространено), то ConfigureAwait(false) будет бессмысленным, поскольку поток продолжает выполнять код в методе после этого и по-прежнему в том же контексте, что и раньше.

...