Search code examples
c#firebaseasynchronousxamarin.iossynchronizationcontext

TaskCompletionSource SynchronizationContext


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.


Solution

  • 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.