TaskCompletionSource SynchronizationContext

Я пишу базовый код Firebase в приложении Xamarin для iOS и сталкиваюсь с классической тупиковой ситуацией с ошибкой TaskCompletionSource.

public Task<string> GetUsers()
{
    var tcs = new TaskCompletionSource<string>();
    _instance.GetChild("users").ObserveSingleEvent(DataEventType.Value,
        x => { tcs.SetResult(x); });
    return tcs.Task;
}

Когда я блокирую этот код так:

var users = GetUsers().Result;

Приложение блокируется.

Если я правильно понимаю, обратный вызов пытается запуститься в том же контексте, что и .Result.

Чего я не понимаю, так это того, что если я изменю свой код, чтобы ожидать вызова GetUsers() в Task, например так:

var result = Task.Run(
    async () => await AppContext.Database.GetUsers().ConfigureAwait(false)
).Result;

Это все еще тупики.

Что здесь происходит во втором случае? Не должен ли тот факт, что код выполняется в другом потоке из-за Task.Run, означать, что внешний .Result не блокирует вызов обратного вызова?

ИЗМЕНИТЬ:

В продолжение комментария Нкоси я спрашиваю об этом, потому что мне любопытно, почему код блокируется. Если я жду звонка

var users = await GetUsers().ConfigureAwait(false);

тогда тупик уходит. Я просто хотел бы понять, почему он блокируется при заключении в Task, потому что, исходя из моего (явно неправильного) понимания Task.Run, этого не должно быть.


person Levi Botelho    schedule 10.06.2017    source источник
comment
Для меня это проблема XY. Покажите, чего вы в конечном итоге пытаетесь достичь, на минимально воспроизводимом примере, и, возможно, решение может быть решено. Это похоже на смесь асинхронных и блокирующих вызовов. поэтому верхний стек вызовов также должен отображаться.   -  person Nkosi    schedule 10.06.2017
comment
@Nkosi На самом деле я не застрял, потому что, если я просто сделаю var users = await Getusers().ConfigureAwait(false), код запустится без взаимоблокировки. Мне просто любопытно, почему версия Task.Run не работает, потому что, насколько я понимаю, должна.   -  person Levi Botelho    schedule 10.06.2017
comment
Ok. мое непонимание вашего вопроса. Знакомы ли вы с этой статьей Стивена Клири msdn.microsoft.com/en-us /magazine/jj991977.aspx   -  person Nkosi    schedule 10.06.2017
comment
Также взгляните на этот stackoverflow.com/questions/32591462/   -  person Nkosi    schedule 10.06.2017
comment
@Nkosi, другой вопрос SO действительно интересен - взлом пула потоков, который он описывает, - это именно то, что я думаю делаю: помещаю асинхронную операцию в другой поток. Начинаю задаваться вопросом, связано ли это с Xamarin... Собираюсь более подробно изучить его статью, на которую есть ссылка в этом вопросе (msdn.microsoft.com/en-us/magazine/mt238404.aspx) и посмотреть, смогу ли я найти там ответ.   -  person Levi Botelho    schedule 10.06.2017


Ответы (1)


ObserveSingleEvent всегда отправляет обратный вызов в поток пользовательского интерфейса (и я думаю, что все или почти все обратные вызовы firebase делают это). Он не захватывает контекст синхронизации или что-то в этом роде — просто всегда отправляет обратный вызов в поток пользовательского интерфейса (помните — это просто оболочка для собственного кода IOS). Поэтому, когда вы блокируете свой поток пользовательского интерфейса, ожидая Result, он заблокируется по очевидным причинам, независимо от того, из какого потока вы вызываете GetUsers. Ссылки, которые вы упомянули, описывают другую ситуацию, когда вызываемый код захватывает текущий контекст синхронизации, поэтому они вызывают этот код из фонового потока, который не имеет контекста синхронизации, и обратные вызовы не будут отправлены в него. Это не тот случай здесь.

person Evk    schedule 10.06.2017