Search code examples
c#async-awaitconfigureawait

Trivial exit from a method that may perform an asynchronous call


I've got an issue calling a method that may return a Task<T> or null depending on the result of an initial synchronous lookup call (this itself might be an anti-pattern so please let me know).

I kind of want to return null if a trivial exit condition occurs but this is causing the calling (outer) method to fail because the outer call expects a Task<T> response (trivial exit) which gets pushed through to ConfigureAwait(true) which subsequently produces a NullReferenceException.

Outer calling method:

var res = await MyService.GetUserCourseStatusAsync(userID, productID).ConfigureAwait(true);

Intermediate method:

public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
    // Look up User's ID (if exists)
    var userCredentials = GetUserCredentials(userID);

    if (userCredentials?.UserID == null)
        return null; // Trivial return of null ("User not registered"). *** This causes exception on ConfigureAwait(true) above ***

    // Another synchronous call
    var courseId = GetCourseID(productID);

    if (courseId == null)
        throw new InvalidOperationException($"Product #{productID} is not a course");

    // Asynchronous call to inner method (this bit works fine)
    return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}

So my thought then is that we should always return a Task<T> instead of null. However, all of these cause compile errors:

//return null; // Trivial return of null ("User not registered"). *** This causes exception 

// Compile error: CS0029: Cannot implicitly convert type 'GetUserCourseInner' to 'System.Threading.Tasks.Task<IGetUserCourseResponse>'
return new GetUserCourseInner(); // Not registered

// Compile error: CS1503    Argument 1: cannot convert from 'GetUserCourseInner' to 'System.Func<IGetUserCourseResponse>'
return new Task<IGetUserCourseResponse>(new GetUserCourseInner()); // Not registered

How do I return a dummy Task<T> that isn't a result of a async call?

Is this even the correct approach?


Solution

  • It would be better, as you suggested, to return a Task<IGetUserCourseResponse> which contains null (or some other sentinel value). You can create such a completed Task with Task.FromResult((IGetUserCourseResponse)null):

    public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
    {
        // Look up User's ID (if exists)
        var userCredentials = GetUserCredentials(userID);
        if (userCredentials?.UserID == null)
            return Task.FromResult((IGetUserCourseResponse)null);
    
        // Another synchronous call
        var courseId = GetCourseID(productID);
        if (courseId == null)
            throw new InvalidOperationException($"Product #{productID} is not a course");
    
        // Asynchronous call to inner method (this bit works fine)
        return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
    }
    

    Alternatively, you could make your outer method async. Note however that this changes its behaviour in the case where it throws an InvalidOperationException: instead of the method throwing this exception directly, it will instead return a Task which contains this exception. This may or may not be what you want:

    public async Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
    {
        // Look up User's ID (if exists)
        var userCredentials = GetUserCredentials(userID);
        if (userCredentials?.UserID == null)
            return null;
    
        // Another synchronous call
        var courseId = GetCourseID(productID);
        if (courseId == null)
            throw new InvalidOperationException($"Product #{productID} is not a course");
    
        // Asynchronous call to inner method (this bit works fine)
        return await GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
    }