odata.net icon indicating copy to clipboard operation
odata.net copied to clipboard

Blazor (wasm): Cannot wait on monitors on this runtime.

Open Molinware opened this issue 3 years ago • 4 comments

Exception Cannot wait on monitors on this runtime throw when calling FirstOrDefault(), ToList() and so on...

Assemblies affected

Microsoft.OData.Client 7.8.3

Reproduce steps

/// Program.cs

// Add ODataServiceContext Service
public static async Task Main(string[] args)
{
    builder.Services.AddScoped(sp => new ODataServiceContext(new Uri(builder.HostEnvironment.BaseAddress), GetEdmModel(builder.HostEnvironment.BaseAddress)));
}

// ODataServiceContext Class -> Constructor
public ODataServiceContext(Uri serviceRoot, IEdmModel model) : base(serviceRoot)
{
    this.HttpRequestTransportMode = HttpRequestTransportMode.HttpClient;
    this.Format.LoadServiceModel = () => model;
    this.Format.UseJson();
}

// ODataServiceContext Class ->  GetEdmModel
public static async Task<IEdmModel> GetEdmModel(Uri serviceRoot)
{
    using (var httpClient = new HttpClient { BaseAddress = serviceRoot })
    using (var stream = await httpClient.GetStreamAsync("Pesquisa/$metadata"))
    using (var reader = XmlReader.Create(stream))
    {
        return CsdlReader.Parse(reader);
    }
}
/// Login Page

// Calling Odata
this.ODataContext.CreateQuery<UserDto>("Users").FirstOrDefault();

Expected result

Return the results

Actual result

Exception Cannot wait on monitors on this runtime.

Molinware avatar Apr 06 '21 14:04 Molinware

@Molinware Seems that this error is observed when making synchronous calls. As a workaround, the alternative is to call ExecuteAsync as opposed to calling the LINQ methods like FirstOrDefault() and ToList()

gathogojr avatar Apr 06 '21 16:04 gathogojr

@gathogojr, @ElizabethOkerio I suspect that in the implementation of the IEnumerable<> returned by CreateQuery there is a blocking call that WASM blocks.

If there was a CreateQueryAsync that returns an IAsyncEnumerable<> and uses the async methods on HttpClient

public async IAsyncEnumerable<T> CreateQueryAsync<T>(string query);

this could work by calling

var first = await this.ODataContext.CreateQueryAsync<UserDto>("Users").FirstOrDefault();

Just a side note: I was not able to find a FirstOrDefault or ToList extension method on IAsyncEnumerable<T> But this would be straightforward to implement together with CreateQueryAsync

public static async Task<List<T>> ToList<T>(this IAsyncEnumerable<T> items)
{
    var result = new List<T>();
    await foreach (var item in items)
    {
        result.Add(item);
    }
    return result;
}

chrisspre avatar Apr 06 '21 16:04 chrisspre

Seems that this error is observed when making synchronous calls. As a workaround, the alternative is to call ExecuteAsync as opposed to calling the LINQ methods like FirstOrDefault() and ToList()

@gathogojr Unfortunally I cannot use ExecuteAsync, I need to bind an IQueryable to the grid, then everything is automated and I have no control over calls, all I do is this:

private IQueryable<UserDto> ODataIQueryable {get; set; } = this.ODataContext.CreateQueryAsync<UserDto>("Users");

<Grid QueryAsync="@ODataIQueryable">
    <GridColumn Field="Id" />
    <GridColumn Field="Name" />
</Grid>

I have no control over Grid and internally it applies whats needed for the call.

Example: If I filter the name in the grid it automatically would do this:

// I don't call it, the Grid does this internally
ODataIQueryable.Where(x => x.Name == "ExampleName").ToList()

Molinware avatar Apr 06 '21 20:04 Molinware

I also ran into this issue using the latest OData.Client 7.9.0 in a Blazor WASM 6.0.0-preview.3 app. There are two workarounds that appear to work:

Option 1: Force cast the query after applying the Where and then call ExecuteAsync:

var items = await ((DataServiceQuery<Message>)DataServiceContext.Message.Where(e => e.Id == Message.Id)).ExecuteAsync();
var dsc = new DataServiceCollection<Message>(items);

Option 2: Use a $filter on the OData collection instead of on the get by key:

// The below line fails with the same exception as reported by the OP
// var items = DataServiceContext.Message.Where(e => e.Id == Message.Id);

// This workaround queries the collection and applies OData $filter
var items = await DataServiceContext.Message.AddQueryOption("$filter", $"id eq {Message.Id}").ExecuteAsync();
var dsc = new DataServiceCollection<Message>(items);

davidyee avatar May 17 '21 09:05 davidyee