8

Основываясь на многочисленных книгах и блогах, включая this excellent one here, ясно, что при записи библиотеки dll, выставляющей вспомогательные асинхронные методы, то есть методы обертки, обычно считается лучшей практикой для внутреннего завершения ввода/вывода задачу фактических асинхронных методов на Threadpool нити так, как (псевдо-код показан ниже для краткости, и я использую HttpClient в качестве примера)C# async/await chaining with ConfigureAwait (false)

public Async Task<HttpResponseMessage> MyMethodAsync(..) 
{ 
    ... 
    var httpClient = new HttpClient(..); 
    var response = await httpClient.PostAsJsonAsync(..).ConfigureAwait(false); 
    ... 
    return response; 
} 

ключевым моментом здесь является использование ConfigureAwait(false) таким образом, что Выполнение задачи ввода-вывода происходит в потоке threadpool вместо исходного контекста потока, что потенциально предотвращает взаимоблокировки.

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

CallerA -> Method1Async -> Method2Async -> finally the above MyMethodAsync 

Является ли это достаточно, чтобы иметь ConfigureAwait(false) на конечном методе или только должен также обеспечить один Method1Async и Method2Async также внутренне называют свои методы асинхронных с ConfigureAwait(false)? Кажется глупым включать все эти промежуточные методы, особенно если Method1Async и Method2Async являются просто перегрузками, которые в конечном итоге вызывают MyMethodAsync. Любые мысли, пожалуйста, просветите нас!

Обновлено в примере Так что, если у меня есть библиотека со следующим частным методом асинхронной,

private async Task<string> MyPrivateMethodAsync(MyClass myClass) 
{ 
    ... 
    return await SomeObject.ReadAsStringAsync().ConfigureAwait(false); 
} 

Я должен убедиться, что следующие публичные перегруженные методы и также включают ConfigureAwait (ложь), как показано ниже?

public async Task<string> MyMethodAsync(string from) 
{ 
     return await MyPrivateMethodAsync(new (MyClass() { From = from, To = "someDefaultValue"}).ConfigureAwait(false); 
} 
public async Task<string> MyMethodAsync(string from, string to) 
{ 
     return await MyPrivateMethodAsync(new (MyClass() { From = from, To = to }).ConfigureAwait(false); 
} 
+0

При использовании 'ConfigureAwait (false)' является хорошей практикой для кода библиотеки, его использование может иметь свои собственные последствия: http://stackoverflow.com/q/28410046 – Noseratio

ответ

9

Определенно нет. ConfigureAwait так же, как и его название, настраивает await. Это влияет только на await в сочетании с ним.

ConfigureAwait фактически возвращает другой awaitable типа, ConfiguredTaskAwaitable вместо Task, который в свою очередь, возвращает другой тип awaiter ConfiguredTaskAwaiter вместо TaskAwaiter

Если вы хотите игнорировать SynchronizationContext для всех ваших await s вы должны использовать ConfigureAwait(false) для каждого из них.

Если вы хотите ограничить использование ConfigureAwait(false) вы можете использовать мой NoSynchronizationContextScope (см here) на самом верху:

async Task CallerA() 
{ 
    using (NoSynchronizationContextScope.Enter()) 
    { 
     await Method1Async(); 
    } 
} 
+1

Я думаю, что «необходимо ... для * каждого * из них «слишком строг» - есть ли у вас 2+ 'await' в строке, и сначала они действительно возвращаются асинхронно, чтобы остальные не нуждались в' ConfigureAwait (false) ', потому что вы потеряли контекст при возврате вызова ... Но на самом деле 'ConfigureAwait (false)' должен быть на всех вызовах (или ни один). –

+1

@ i3arnon быстрый вопрос о вашем NoSynchronizationContextScope, который отлично выглядит, и я хотел бы понять его прецедент. Следуя приведенному выше примеру, ru говорит, что с небольшим количеством дополнительного кодирования, включающим ваш NoSynchronizationContextScope, CallerA теперь может инструктировать методы в дереве вызовов, то есть Method1Async -> Method2Async ->, вплоть до MyMethodAsync, чтобы временно игнорировать SC независимо от того, или нет, эти методы имеют ConfigureAwait (false) в них? – SamDevx

+1

@SamDevx почти все. Это на самом деле суровее. Он временно удаляет SC, поэтому внутренние методы не имеют SC для игнорирования. – i3arnon

4

Когда задача ожидается, она создает соответствующий TaskAwaiter, чтобы следить за задача, которая также фиксирует текущий SynchronizationContext. После завершения задачи awaiter запускает код после ожидания (называемого продолжением) в этом захваченном контексте.

Вы можете предотвратить это путем вызова ConfigureAwait(false), который создает различные виды awiatable (ConfiguredTaskAwaitable) и соответствующий awaiter (ConfiguredTaskAwaitable.ConfiguredTaskAwaiter), не запустить продолжение на захваченном контексте.

Дело в том, что для каждого await создается другой экземпляр awaiter, это не то, что разделяется между всеми awaitables в методе или программе. Поэтому лучше всего вызывать ConfigureAwait(false) за каждое заявление await.

Вы можете увидеть исходный код ожидающих here.

+0

Небольшая коррекция: при создании 'Task' она делает * not * захватывает текущий' SynchronizationContext'. Когда вызывается 'Task.GetAwaiter()' (сгенерированным компилятором кодом для 'await'), в этот момент захватывается' SynchronizationContext'. – Noseratio

+0

@Noseratio спасибо за ваш комментарий. Я получил это из [источника задания] (http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs,16e40fc537484d93). 'Task.GetAwaiter()', похоже, не делает этого, хотя я и думал, что это так. Мне, должно быть, что-то не хватает –

+0

Я не сказал, что 'TaskAwaiter 'захватывает SC, когда он создается. Он делает это в своей реализации 'ICriticalNotifyCompletion.OnCompleted/UnsafeOnCompleted' [здесь] (http://referencesource.microsoft.com/#mscorlib/system/runtime/compilerservices/TaskAwaiter.cs,45f10a20f8fdfd61, ссылки), который является ключом метод любого awaiter. – Noseratio

 Смежные вопросы

  • Нет связанных вопросов^_^