2

I'm writing some basic Firebase code in a Xamarin iOS app and am running into a classic deadlock situation with a TaskCompletionSource.

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

When I block on this code like so:

var users = GetUsers().Result;

The application deadlocks.

If I understand correctly, the callback is trying to run on the same context that the .Result is waiting on.

What I don't understand is that if I modify my code to await the GetUsers() call in a Task like so:

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

It still deadlocks.

What's going on here in the second case? Shouldn't the fact that the code is running on another thread due to the Task.Run mean that the outside .Result doesn't block the callback invocation?

EDIT:

Following up on Nkosi's comment I'm asking this because I'm curious as to why the code is blocking. If I await the call

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

then the deadlock goes away. I'd just like to understand why it blocks when wrapped in a Task because based on my (clearly incorrect) understanding of Task.Run, it shouldn't.

Levi Botelho
  • 24,626
  • 5
  • 61
  • 96
  • To me this appears to be an XY problem. Show what it is you are ultimately trying to achieve in a [mcve] and may be a solution can be resolved. It looks like a mixing of async and blocking calls. so the upper call stack should be shown as well. – Nkosi Jun 10 '17 at 11:24
  • @Nkosi I'm actually not stuck because if I just do `var users = await Getusers().ConfigureAwait(false)` then the code runs without a deadlock. I'm just curious why the `Task.Run` version doesn't work because based on my understanding it should. – Levi Botelho Jun 10 '17 at 11:28
  • 1
    Ok. my misunderstanding of your question. Are you familiar with this article by Stephen Cleary https://msdn.microsoft.com/en-us/magazine/jj991977.aspx – Nkosi Jun 10 '17 at 11:29
  • Also take a look at this https://stackoverflow.com/questions/32591462/is-using-an-an-async-lambda-with-task-run-redundant – Nkosi Jun 10 '17 at 11:38
  • @Nkosi the other SO question is really interesting -- the "thread pool hack" that he describes is exactly what I *think* i'm doing: putting the async operation on another thread. Starting to wonder if this is linked to Xamarin... Going to look more deeply into his article referenced in that question (https://msdn.microsoft.com/en-us/magazine/mt238404.aspx) and see if I can find the answer in there. – Levi Botelho Jun 10 '17 at 12:02

1 Answers1

3

ObserveSingleEvent always dispatches callback to UI thread (and I think all or almost all firebase callbacks do that). It does not capture synhronization context or something like that - just always dispatches callback to UI thread (remember - it's just a wrapper around native IOS code). So when you block your UI thread by waiting on Result - it will deadlock for obvious reasons, regardless of from which thread you call GetUsers. Links you mention describe another situation when called code captures current synhronization context, so they call that code from background thread which has no synhronization context and callbacks will not be posted to it. That's not the case here.

Evk
  • 98,527
  • 8
  • 141
  • 191