Почему 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было в силе, когда была создана задача асинхронного метода.

Другие вопросы по тегам