odata.net
odata.net copied to clipboard
Blazor (wasm): Cannot wait on monitors on this runtime.
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 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, @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;
}
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()
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);