Тупик даже после использования ConfigureAwait(false) в потоке Asp.Net
Я захожу в тупик даже после использования ConfigureAwait(false)
ниже приведен пример кода.
В соответствии с примером http://blog.stephencleary.com/2012/02/async-and-await.html (#Avoding Context), это не должно было быть заблокировано.
Это мой класс:
public class ProjectsRetriever
{
public string GetProjects()
{
...
var projects = this.GetProjects(uri).Result;
...
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
return await this.projectSystem.GetProjects(uri, Constants.UserName).ConfigureAwait(false);
}
}
Этот класс из общей библиотеки:
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects();
// code here is never hit
...
}
Работает, если я добавляю ConfigureAwait(false) для ожидания вызова в общей библиотеке, где выполняется вызов HttpClient:
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects().ConfigureAwait(false);
// no deadlock, resumes in a new thread.
...
}
Я просматриваю все найденные блоги, единственное отличие, которое я нахожу, - ConfigureAwait(false) работает при использовании вызова httpClient.AsyncApi()!?
Пожалуйста, помогите уточнить!!!
3 ответа
Из комментариев:
Я предполагал, что когда ConfigureAwait(false) используется (где угодно в стеке вызовов), выполнение с этой точки не приведет к взаимоблокировке.
Я не верю в черную магию, и вы не должны. Всегда старайтесь понять, что происходит, когда вы используете что-то в своем коде.
Когда ты await
асинхронный метод, который возвращает Task
или Task<T>
есть неявный захват SynchronizationContext
посредством TaskAwaitable
генерируется Task.GetAwaiter
метод.
Как только этот контекст синхронизации будет создан и вызов асинхронного метода завершится, TaskAwaitable
пытается маршализовать продолжение (которое в основном является остальной частью вызовов метода после первого await
ключевое слово) на SynchronizationContext
(с помощью SynchronizationContext.Post
) который был ранее захвачен. Если вызывающий поток заблокирован, ожидая завершения того же метода, вы попадаете в тупик.
Вы должны спросить себя, должен ли я предоставлять синхронные оболочки для асинхронных методов? 99 процентов времени ответ - нет. Вы должны использовать синхронный API, такой как один WebClient
предложения.
Блокируется при использовании в ProjectsRetriever
так как:
public class ProjectsRetriever
{
public string GetProjects()
{
//querying the result blocks the thread and wait for result.
var projects = this.GetProjects(uri).Result;
... //require Thread1 to continue.
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
//any thread can continue the method to return result because we use ConfigureAwait(false)
return await this.projectSystem.GetProjects(uri, Constants.UserName).ConfigureAwait(false);
}
}
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects();
// code here is never hit because it requires Thread1 to continue its execution
// but Thread1 is blocked in var projects = this.GetProjects(uri).Result;
...
}
Не блокируется при использовании в ProjectSystem
так как:
public class ProjectsRetriever
{
public string GetProjects()
{
...
var projects = this.GetProjects(uri).Result;
...//requires Thread1 to continue
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
//requires Thread1 to continue
return await this.projectSystem.GetProjects(uri, Constants.UserName);
}
}
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects().ConfigureAwait(false);
// no deadlock, resumes in a new thread. After this function returns, Thread1 could continue to run
}
У меня такая же проблема. "ConfigureAwait(false)" не всегда может избежать блокировки.
public class HomeController : Controller
{
public async Task<ActionResult> Index()
{
// This works !
ViewBag.Title = GetAsync().Result;
// This cause deadlock even with "ConfigureAwait(false)" !
ViewBag.Title = PingAsync().Result;
return View();
}
public async Task<string> GetAsync()
{
var uri = new Uri("http://www.google.com");
return await new HttpClient().GetStringAsync(uri).ConfigureAwait(false);
}
public async Task<string> PingAsync()
{
var pingResult = await new Ping().SendPingAsync("www.google.com", 3).ConfigureAwait(false);
return pingResult.RoundtripTime.ToString();
}
}
Для приведенного выше кода "GetAsync()" работает, а "PingAsync()" - нет.
Но я обнаружил, что если я оберну асинхронный вызов в новую задачу и подожду этой задачи, PingAsync() сработает без "ConfigureAwait(false)":
var task = Task.Run(() => PingAsync());
task.Wait();
ViewBag.Title = task.Result;
Я не знаю причину, может кто-то может сказать мне разницу.