Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Если вы разрабатываете стороннюю библиотеку, очень важно всегда настраивать await таким образом, чтобы остальная часть метода была выполнена произвольным потоком из пула.Вот только это заставит вас писать тот самый некрасивый код, который вы так хотели избежать. Браво! А проблема всего лишь в том, что не надо вызывать
get_Result вручную, блокируя поток.ConfigureAwait, а через Task.Run.… использовать асинхронную версию метода Read:Вот только конструктор
FileStream блокирующий, и вы блокируете UI поток. Метод, который вы отмели как «некорректный», гораздо корректнее вашего.Если же такой причины нет, то запрещать ей возвращаться в оригинальный поток надо не средствами ConfigureAwait, а через Task.Run.Из вашего объяснения я не понял, почему следует использовать
Task.Run вместо ConfigureAwait. Не могли бы вы пояснить этот момент более подробно?Вот только это заставит вас писать тот самый некрасивый код, который вы так хотели избежать. Браво!
private async void btnRead_Click(object sender, EventArgs e)
{
Context currentContext1 = Thread.CurrentContext;
int result = await DoSomeWorkAsync();
Context currentContext2 = Thread.CurrentContext;
bool areEqual = ReferenceEquals(currentContext1, currentContext2);
}
private async Task<int> DoSomeWorkAsync()
{
await Task.Delay(100).ConfigureAwait(false);
return 1;
}
… в коде сторонних библиотек всегда необходимо добавлять ConfigureAwait(false).Отсюда я делаю вывод, что
DoSomeWorkAsync — это код библиотеки. Если ей нужно зачем-то переключать контекст, то это происходит, скорее всего, для вызова колбека, в котором вам придется использовать ручное переключение контекста, если библиотека делает такую гадость, как ConfigureAwait(false). Если же библиотеке не нужно переключать контекст, то её код не является асинхронным, и помечать его асинхронным не требуется, а для последующего исполнения в другом потоке можно и нужно использовать, например, Task.Run.private async Task F1()
{
// 1. Контекст тот же, что у вызывающего метода
await F2();
// 4. Контекст тот же, что у вызывающего метода,
// ConfigureAwait(false) в F1 не повлиял на смену контекста в этом методе
}
private async Task F2()
{
// 2. Контекст тот же, что у вызывающего метода
await Task.Delay(100).ConfigureAwait(false);
// 3. Нет контекста. Поток, выполняющий эту часть метода - произвольный из пула
}А проблема всего лишь в том, что не надо вызывать get_Result вручную, блокируя поток.
Когда какой-то библиотеке надо вернуться в оригинальный контекст, то на это скорее всего есть веская причина — например, библиотека хочет вызвать колбек. Если же такой причины нет, то запрещать ей возвращаться в оригинальный поток надо не средствами ConfigureAwait, а через Task.Run.
Task.Run, хотя я не уверен, что это сработает в ASP.NET.void button1_Click(object sender, EventArgs e)
{
object result = Task.Run(async () => await DoSomeWorkAsync()).Result;
}
async Task<object> DoSomeWorkAsync()
{
await Task.Delay(100);
return null;
}async void button1_Click(object sender, EventArgs e)
{
await DoSomeWorkAsync(ReportProgress);
}
void ReportProgress()
{
if (this.InvokeRequired)
throw new Exception();
}
async Task DoSomeWorkAsync(Action progressReporter)
{
progressReporter();
await Task.Delay(100).ConfigureAwait(false);
progressReporter();
}async void button1_Click(object sender, EventArgs e)
{
var progressReporter = new Progress<object>(ReportProgress);
await DoSomeWorkAsync(progressReporter);
}
void ReportProgress(object value)
{
if (this.InvokeRequired)
throw new Exception();
}
async Task DoSomeWorkAsync(IProgress<object> progressReporter)
{
progressReporter.Report(null);
await Task.Delay(100).ConfigureAwait(false);
progressReporter.Report(null);
}
вешает UI поток при частых вызовахОграничьте количество репортов или не пихайте в обработчик репортов тяжеловесный код, способный повесить поток?
get_Result. Вместо того, чтобы решить эту проблему на стороне клиента, где она и должна решаться, например, через Task.Run (30 символов).get_Result. Если вы мешаете асинхронный и синхронный код, то вы ССЗБ и должны быть готовы к появлению проблем. К повсеместному использованию .ConfigureAwait(false), как мне кажется, это отношения не имеет.Конечно, мы можем использовать await вместо обращения к свойству Result для того, чтобы избежать дедлока— и написал бы, что нам нужно использовать
await вместо get_Result. За исключением случаев, где это невозможно, но, мол, тогда смотрите сами.public async Task<SomeResult> FooAsync()
{
return await BarAsync();
}
public Task<SomeResult> FooAsync()
{
return BarAsync();
}
return SometingAsync();
используется часто, то понять что-же именно произошло становится сложнее.если вы бросите исключение перед return, то в первом случае оно будет выкинуто при вызове get_Result либо при await, а во втором — сразу.
await Task.Factory.StartNew(() => Thread.Sleep(100));
Async/await в C#: подводные камни