Make async query execution compatible with WASM#3474
Open
gathogojr wants to merge 2 commits intoOData:mainfrom
Open
Make async query execution compatible with WASM#3474gathogojr wants to merge 2 commits intoOData:mainfrom
gathogojr wants to merge 2 commits intoOData:mainfrom
Conversation
b0028e9 to
0e4adf3
Compare
xuzhg
reviewed
Jan 13, 2026
| { | ||
| InvalidOperationException exception = WebUtil.GetHttpWebResponse(ex, ref this.batchResponseMessage); | ||
|
|
||
| // For non-async batch requests we rethrow the WebException. This is shipped behavior. |
Member
There was a problem hiding this comment.
Can you also update the comments to make sure it's still readable later?
xuzhg
reviewed
Jan 13, 2026
| if (this.batchResponseMessage != null) | ||
| { | ||
| // For non-async batch requests we call the test hook to get the response stream but we cannot consume it | ||
| // because we rethrow what we caught and the customer need to be able to read the response stream from the WebException. |
Member
There was a problem hiding this comment.
Can you also update the comments to make sure it's still readable later?
xuzhg
reviewed
Jan 13, 2026
| /// </summary> | ||
| /// <param name="cancellationToken">Optional cancellation token.</param> | ||
| /// <returns>A task that represents the asynchronous batch request operation.</returns> | ||
| internal async Task BatchRequestAsync(CancellationToken cancellationToken = default) |
xuzhg
reviewed
Jan 13, 2026
| .ConfigureAwait(false); | ||
| } | ||
|
|
||
| // TODO: In future releases, expose this method as public GetAllPagesAsync to support IAsyncEnumerable |
Member
There was a problem hiding this comment.
why do we wait for future release since we support it
Contributor
Author
There was a problem hiding this comment.
@xuzhg Because in the current version, there's already a GetAllPagesAsync method that returns IEnumerable<T>. We'd switch that with IAsyncEnumerable<T> in a major release because it'd be a breaking change
xuzhg
reviewed
Jan 13, 2026
| this.outputResponseStream = null; | ||
| } | ||
| else if (copy.Position < copy.Length) | ||
| { // In Silverlight, generally 3 bytes less than advertised by ContentLength are read |
Member
xuzhg
reviewed
Jan 13, 2026
| if (buffer == null) | ||
| { | ||
| refBuffer = buffer = new byte[1000]; | ||
| refBuffer = buffer = new byte[1000]; // Why does this use 1000 instead of DefaultBufferSizeForStreamCopy? |
Member
There was a problem hiding this comment.
Did you check the history to see "why"?
0e4adf3 to
88a2eb8
Compare
60aff18 to
240a800
Compare
240a800 to
28f1005
Compare
2a3a08e to
fb311b7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issues
This pull request fixes #3452.
Description
This PR introduces a modernized, end‑to‑end asynchronous request/response path for OData Client that removes synchronous waits from async flows, restores Blazor WASM compatibility, and improves cancellation/timeout behavior—without breaking existing public APIs. It lays the groundwork for deprecating APM (Begin/End) in the next major release.
Motivation
Task.Wait()afterSendAsync, surfacing “Cannot wait on monitors on this runtime.”HttpCompletionOption.ResponseHeadersReadallowed faster cancellation but exposed latent blocking in downstream conversion that synchronously waited onReadAsStreamAsync().Begin/End+FromAsync), not truly async/await, making timeouts and cancellation brittle across runtimes.Design Overview
New async-native pathway
Introduce and route async callers through a unified, non-blocking pipeline:
This path never blocks and honours cancellation via linked tokens (abort + per‑request timeout + external token).
Cancellation/Timeout
All sends use a linked CancellationToken that merges:
Abort()),This yields prompt, deterministic cancellation across stages (pre‑content, headers, body).
Key Code Changes
HTTP transport (HttpClientRequestMessage)
ConvertHttpWebResponseAsync(HttpResponseMessage)andGetResponseAsync(CancellationToken).CreateSendTask(CancellationToken)to build a linked token source and to send withResponseHeadersReadfor earlier cancellation; apply cached content headers prior to send.Request/response surface (selected)
ODataRequestMessageWrapper.GetResponseAsync(CancellationToken)RequestInfo.GetResponseAsync(ODataRequestMessageWrapper, bool, CancellationToken)QueryResult.ExecuteQueryAsync,GetReadStreamResult.ExecuteQueryAsync, etc.) use the new async response and streaming logic.High-level APIs
DataServiceQuery<T>.ExecuteAsync(...),DataServiceActionQuerySingle<T>.GetValueAsync(...), and other public async APIs continue to exist but now route through the modernized path under the hood.API Surface (additions / changes)
Key new / updated async members (representative):
Transport / messaging
DataServiceClientRequestMessage.GetResponseAsync(CancellationToken)(virtual)HttpClientRequestMessage.GetResponseAsync(CancellationToken)/CreateSendTask(CancellationToken)/ConvertHttpWebResponseAsync(...)ODataRequestMessageWrapper.GetResponseAsync(CancellationToken)(internal)RequestInfo.GetResponseAsync(...)(internal)Query & streaming
QueryResult.ExecuteQueryAsync(CancellationToken)/ProcessResponseStreamAsync(CancellationToken)(internal)GetReadStreamResult.ExecuteQueryAsync(CancellationToken)(internal)Save / batch flows
SaveResult.CreateNextChangeAsync(CancellationToken)BatchSaveResult.BatchRequestAsync(CancellationToken)DeepInsertSaveResult.DeepInsertRequestAsync<T>(T, CancellationToken)BulkUpdateSaveResult.BulkUpdateRequestAsync<T>(CancellationToken, T[])DataServiceContext.*AsyncmethodsChecklist (Uncheck if it is not completed)
Additional work necessary
If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.