Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extension method not equivalent to direct call

I would say the following two code snippets I have are equivalent, but they aren't.

The following is working correctly:

var entry3 = Task.Run(async () => await entry2.GetMemberGroupsAsync(false)).WaitForResult().FirstOrDefault();

The following code, where I just moved the Task.Run.WaitForResult chain into an extension method, isn't working, but produces a deadlock:

var entry3 = entry2.GetMemberGroupsAsync(false).RunSynchronouslyAndReturnResult().FirstOrDefault();

public static T RunSynchronouslyAndReturnResult<T>(this Task<T> task)
{
    return Task.Run(async () => await task).WaitForResult();
}

Why aren't these two code snippets equivalent?

For completeness's sake, the GetMemberGroupsAsync method is provided by Microsoft Azure Graph API, and the function WaitForResult is defined below. As far as I can see, it doesn't do anything different depending on the caller name or sth. like that:

public static TResult WaitForResult<TResult>(this Task<TResult> task,
                                             bool continueOnCapturedContext = false)
{
    if (task == null)
    {
        throw new ArgumentNullException("task");
    }

    try
    {
        return PreventForDeadLocks(task, continueOnCapturedContext).Result;
    }
    catch (AggregateException ex)
    {
        if (ex.InnerExceptions.Count == 1)
        {
            throw ex.InnerExceptions[0];
        }

        throw;
    }
}

public static async Task<TResult> PreventForDeadLocks<TResult>(this Task<TResult> task,
                                                               bool continueOnCapturedContext = false)
{
    return await task.ConfigureAwait(continueOnCapturedContext: continueOnCapturedContext);
}
like image 443
Alexander Avatar asked Mar 13 '23 01:03

Alexander


2 Answers

The difference here is in which synchronization context your task started. Here:

var entry3 = Task.Run(async () => await entry2.GetMemberGroupsAsync(false)).WaitForResult().FirstOrDefault();

you start your async task (I mean await entry2.GetMemberGroupsAsync(false)) inside Task.Run call, so UI synchronization context is not captured. But here:

var entry3 = entry2.GetMemberGroupsAsync(false).RunSynchronouslyAndReturnResult().FirstOrDefault();

You implicitly start your task (entry2.GetMemberGroupsAsync(false) returns Task) on UI context, so UI synchronization context is captured, and you have your deadlock.

like image 144
Evk Avatar answered Mar 25 '23 03:03

Evk


In the first case, GetMemberGroupsAsync is called on a different thread than WaitForResult.

In the second case, it is called on the same thread as WaitForResult. You're just awaiting on a different thread.

like image 42
Eren Ersönmez Avatar answered Mar 25 '23 03:03

Eren Ersönmez