简体   繁体   中英

Async method in Xamarin.Forms ViewModel not waiting for AzureServiceTokenProvider and SqlConnection to initialize

(Edited to add more detail about every call I'm making)

I have a Xamarin Forms application connecting to a .Net Core 2.2 web service hosted in Azure App Services.

In my view model I have a call like this:

private async Task GetItems() {            
    var result = await itemsListFactory.GetItemsAsync()
}

Which calls this:

public async Task<IEnumerable<IItemInfo>> GetItemsAsync() {
    return await ItemList.GetItemListAsync();
}

Which calls this (CSLA business object):

  public static async Task<ItemList> GetItemListAsync() {
        return await DataPortal.FetchAsync<ItemList>();
    }

Which calls this:

[Fetch]
private async void DataPortal_Fetch() {
    var rlce = RaiseListChangedEvents;
    RaiseListChangedEvents = false;
    IsReadOnly = false;

    using (var ctx = Dal.DalFactory.GetManager()) {
        var dal = ctx.GetProvider<IItemDal>();
        List<ItemDto> list = null;

        list = await dal.FetchAsync();

        foreach (var item in list) {
            Add(DataPortal.FetchChild<ItemInfo>(item));                    
        }
    }

    IsReadOnly = true;
    RaiseListChangedEvents = rlce;
}

Which calls:

public async Task<List<ItemDto>> FetchAsync() {

    var resultSet = new List<ItemDto>();

    var connectionManager = ServiceLocator.Current.GetInstance<IAzureConnectionManager>();

    using (var conn = await connectionManager.GetOpenConnectionAsync()) {
        /* Reading from DB */                    
    }

    return resultSet;         
}

The implementation of the AzureConnectionManager looks like this:

public async Task<SqlConnection> GetOpenConnectionAsync()
{            
    var accessToken = await new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net/");
    var connection = new SqlConnection(dbconnection) {
        AccessToken = accessToken
    };

    await connection.OpenAsync();

    return connection;
}

However, the first time I make this call (eg first call of the day, or after not using the service for a while) I get no results back. Any subsequent calls seem to work just fine. My guess is this has something to do with the service having to take a few "extra steps" to return data due to inactivity.

This suspicion seems to be confirmed whenever I debug the web service and set breakpoints in my view model as well as the server-side code. Whenever the service's call returns with no records it's almost as if it's returning early from the server, because it returns to the view model with no data, and then my debugger hops back onto the server after it's received the access token. So, it's as if my code decided not to wait for the GetAccessTokenAsync and OpenAsync to finish what they had to do before returning to the client.

I can fix this by adding a .Result to GetAccessTokenAsync() and .Wait() to OpenAsync() like this:

public async Task<SqlConnection> GetOpenConnectionAsync()
        {            
            var accessToken = new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net/").Result;
            var connection = new SqlConnection(dbconnection) {
                AccessToken = accessToken
            };

            connection.OpenAsync().Wait();

            return connection;
        }

But this feels like a hack.

I doubt this is the way I'm supposed to fix this, but maybe it is. At the very least I'd like to just understand what's going on here if this is the correct way to handle this situation.

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any. When the await operator is applied to the operand that represents already completed operation, it returns the result of the operation immediately without suspension of the enclosing method. The await operator doesn't block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method.

Official Document on this

So if we look at the what the documents say about Async/Await you'll notice that

When the await operator is applied to the operand that represents already completed operation, it returns the result of the operation immediately without suspension of the enclosing method.

More then likely OpenAsync(); Is seen as a operand that's already completed as you might not be awaiting your Returns, So the Operation Runs retrieve's your data but because your not suspending anything in OpenAsync It might assume the operand is already completed on the first instance and just continue then the data is loaded so on your second attempt you have the data to work with, as its already populated on the first try.

So i'd like to see a bit more code actually.

However one thing I will say is that .Wait() is Bad If you have to wait for a result and force that wait the better way to do this is .GetAwaiter().GetResult() I can link you a Seminar that explains in details about this. But in Essence .Wait() Throws exceptions into the void and make them extremly difficult to track(Or at-least far more difficult then you'd want them to be)

"Also note in no way am I anywhere near a Expert in Async/Await so feel free to correct me"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM