Ответ 1
Кажется, вы считаете, что установка любого контекста синхронизации может привести к взаимоблокировке с асинхронным кодом - это неверно. Опасно блокировать асинхронный код в приложениях asp.net и UI, потому что у них есть специальный, одиночный, основной поток. В приложениях пользовательского интерфейса, который является, ну, основным потоком пользовательского интерфейса, в приложениях ASP.NET существует много таких потоков, но для данного запроса есть однопроцессорный поток.
Контексты синхронизации приложений ASP.NET и UI отличаются тем, что они в основном отправляют обратные вызовы в этот специальный поток. Поэтому, когда:
- вы выполняете код в этом потоке
- из этого кода вы выполняете некоторое async
Task
и блокируете егоResult
. - В
Task
есть выражение ожидания.
Появится тупик. Почему это происходит? Поскольку продолжение асинхронного метода Post
ed в текущий контекст синхронизации. Те специальные контексты, которые мы обсудим выше, отправят эти продолжения в специальный основной поток. Вы уже выполняете код в этом же потоке, и он уже заблокирован - значит, тупик.
Так что ты делаешь неправильно? Во-первых, SynchronizationContext
не является особым контекстом, о котором мы говорили выше, - он просто публикует продолжения в поток потока потока. Тебе нужен еще один тест. Вы можете использовать существующие (например, WindowsFormsSynchronizationContext
) или создать простой контекст, который ведет себя одинаково (пример кода, ТОЛЬКО для демонстрационных целей):
class QueueSynchronizationContext : SynchronizationContext {
private readonly BlockingCollection<Tuple<SendOrPostCallback, object>> _queue = new BlockingCollection<Tuple<SendOrPostCallback, object>>(new ConcurrentQueue<Tuple<SendOrPostCallback, object>>());
public QueueSynchronizationContext() {
new Thread(() =>
{
foreach (var item in _queue.GetConsumingEnumerable()) {
item.Item1(item.Item2);
}
}).Start();
}
public override void Post(SendOrPostCallback d, object state) {
_queue.Add(new Tuple<SendOrPostCallback, object>(d, state));
}
public override void Send(SendOrPostCallback d, object state) {
// Send should be synchronous, so we should block here, but we won't bother
// because for this question it does not matter
_queue.Add(new Tuple<SendOrPostCallback, object>(d, state));
}
}
Все, что он делает, помещает все обратные вызовы в одну очередь и выполняет их один за другим в отдельном отдельном потоке.
Имитировать тупик в этом контексте легко:
class Program {
static void Main(string[] args)
{
var ctx = new QueueSynchronizationContext();
ctx.Send((state) =>
{
// first, execute code on this context
// so imagine you are in ASP.NET request thread,
// or in WPF UI thread now
SynchronizationContext.SetSynchronizationContext(ctx);
Deadlock(new Uri("http://google.com"));
Console.WriteLine("No deadlock if got here");
}, null);
Console.ReadKey();
}
public static void NoDeadlock(Uri uri) {
DeadlockingGet(uri).ContinueWith(t =>
{
Console.WriteLine(t.Result);
});
}
public static string Deadlock(Uri uri)
{
// we are on "main" thread, doing blocking operation
return DeadlockingGet(uri).Result;
}
public static async Task<string> DeadlockingGet(Uri uri) {
using (var http = new HttpClient()) {
// await in async method
var response = await http.GetAsync(uri);
// this is continuation of async method
// it will be posted to our context (you can see in debugger), and will deadlock
response.EnsureSuccessStatusCode();
return response.Content.ReadAsStringAsync().Result;
}
}
}