Почему ConfigureAwait(false) не работает, пока Task.Run() работает?
Я вызываю асинхронный библиотечный метод с ConfigureAwait(false). Но я все еще в тупике. (Я использую его в API контроллера ASP.NET) Но, если я использую тот же метод, заключенный в Task.Run(), он работает нормально.
Насколько я понимаю, если метод библиотеки не использует ConfigureAwait внутри, то добавление ConfigureAwait не решит проблему, так как при вызове библиотеки это приведет к взаимоблокировке (мы блокируем его, используя.Result). Но, если это так, почему он работает в Task.Run(), поскольку он не сможет продолжить работу в том же контексте / потоке.
Эта статья говорит об этом. Кстати, у меня есть много статей Стивена Клири. Но почему Task.Run() работает, остается загадкой.
Фрагмент кода:
// This Create Method results in Deadlock
public async Task<string> Create(MyConfig config)
{
Document doc = await Client.CreateDocumentAsync(CollectionUri, config).ConfigureAwait(false);
return doc.Id;
}
// Uses Task.Run() which works properly, why??
public string Create(MyConfig config)
{
Document doc = Task.Run(() => Client.CreateDocumentAsync(CollectionUri, config)).Result;
return doc.Id;
}
[HttpPost]
public ActionResult CreateConfig(MyConfig config)
{
string id = Create(config).Result;
return Json(id);
}
2 ответа
В первом примере реализации Client.CreateDocumentAsync
блокируется, потому что пытается выполнить продолжение, используя текущий SynchronizationContext
,
Когда используешь Task.Run
, делегат будет вызываться в потоке ThreadPool, это означает, что ток не будет SynchronizationContext
поэтому все продолжения будут возобновлены с использованием потока ThreadPool. Это означает, что это не будет тупик.
Из интереса, почему ваш CreateConfig
метод не асинхронный? Самые последние версии MVC и WebAPI поддерживают асинхронные методы, избавляя от .Result
было бы лучшим решением.
Я верю, что Луказоид прав. Другими словами...
// This Create Method results in Deadlock
public async Task<string> Create(MyConfig config)
{
Document doc = await Client.CreateDocumentAsync(CollectionUri, config).ConfigureAwait(false);
return doc.Id;
}
Вы не можете просто вставить ConfigureAwait(false)
на одном уровне и волшебным образом предотвратите тупики. ConfigureAwait(false)
может только предотвратить взаимные блокировки, если он используется каждым await
в транзитивном замыкании этого метода и всех методов, которые он вызывает.
Другими словами, ConfigureAwait(false)
должен быть использован для каждого await
в Create
(что это), и это также должно быть использовано для каждого await
в CreateDocumentAsync
(что мы не знаем), и это также должно быть использовано для каждого await
в каждом методе, который CreateDocumentAsync
звонки и т. д.
Это одна из причин, почему это такое хрупкое "решение" тупиковой проблемы.
Просто наблюдение: я также заметил, что это тоже приведет к тупику
private string Create(Task<Document> task)
{
var doc = Task.Run(() => task).Result;
return doc.Id;
}
[HttpPost]
public ActionResult CreateConfig(MyConfig config)
{
var task = Client.CreateDocumentAsync(CollectionUri, config);
var id = Create(task).Result;
return Json(id);
}
Поэтому даже запуск чего-либо в пуле потоков может быть не лучшим решением. Кажется, не менее важным фактором является то, чтоSynchonizationContext
было в силе, когда была создана задача асинхронного метода.