-
Notifications
You must be signed in to change notification settings - Fork 352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement asynchronous support in ODataJsonLightBatchWriter #2111
Implement asynchronous support in ODataJsonLightBatchWriter #2111
Conversation
c52bf6e
to
fca52e2
Compare
fca52e2
to
c45de75
Compare
src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.OData.Core/JsonLight/ODataJsonLightBatchWriter.cs
Outdated
Show resolved
Hide resolved
c45de75
to
653a8f7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
// Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state. | ||
.FollowOnSuccessWithTask(task => this.FlushAsync()); | ||
.FollowOnSuccessWithTask(task => this.FlushAsync()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, do you know why ContinueWith
is not used here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or just using await
on the second statement, i.e.
await this.WriteEndBatchImplementationAsync();
await this.FlushAsync();
What else is FollowOnSuccessWithTask
doing behind the scenes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can think of FollowOnSuccessWithTask
as a "syntactic" sugar for ContinueWith
, although it's a little more than that. We actually use ContinueWith
in the method. The method is designed to propagate failures if the antecedent task fails. If WriteEndBatchImplementationAsync
throws an ODataException
, we propagate that to the caller - not an AggregateException
like ContinueWith
would do.
The delegate passed to FollowOnSuccessWithTask
will not be executed if the antecedent task fails. Specifically in this case, we do not wish to execute FlushAsync
if WriteEndBatchImplementationAsync
fails.
The method is defined in TaskUtils
class just in case you'd like to study the implementation further.
return TaskUtils.GetTaskForSynchronousOperation(() => this.WriteStartChangesetImplementation(changesetId)) | ||
.FollowOnSuccessWith(t => this.FinishWriteStartChangeset()); | ||
await this.WriteStartChangesetImplementationAsync(changesetId) | ||
.FollowOnSuccessWith(t => this.FinishWriteStartChangeset()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, what's the difference between FollowOnSuccessWithTask
and FollowOnSuccessWith
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there async versions of this.FinishWriteStartChangeSet()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FollowOnSuccessWithTask
supports delegate that return another Task instance while FollowOnSuccessWith
doesn't. As an illustration consider the following two usages:
Task.Factory.StartNew(() =>
{
// Do work
return new Random().Next(1, 100);
}).FollowOnSuccessWith((antecedentTask) =>
{
Console.WriteLine("Result: {0}", antecedentTask.Result);
});
// vs.
Task.Factory.StartNew(() =>
{
// Do work
// return new Random().Next(1, 100);
}).FollowOnSuccessWithTask((antecedentTask) =>
{
return Task.Factory.StartNew(() => Console.WriteLine("Result: {0}", antecedentTask.Result));
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no async version of FinishWriteStartChangeSet
. The private method currently doesn't do any writing. It's an implementation detail so it'd be easy to introduce an async equivalent if the synchronous version evolved to doing any writing.
{ | ||
Debug.Assert(outputStream != null, "outputStream != null"); | ||
Debug.Assert(operationListener != null, "operationListener != null"); | ||
|
||
return new ODataWriteStream(outputStream, operationListener); | ||
return new ODataWriteStream(outputStream, operationListener, synchronous); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you remind me what the synchronous
argument does again? I remember it from a previous PR but don't remember what it was used for. I think it was used in the dispose methods, but I'm not quite sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reference to #2109, ODataWriteStream
previously had no way of telling whether we're writing synchronously or asynchronously. We address that gap by introducing this argument such that when the Dispose
method is invoked, either of StreamDisposed
or StreamDisposedAsync
is called depending on the value passed for the argument.
This approach is necessitated by the fact that we support NET45
and NETSTANDARD1_1
both of which do not support IAsyncDisposable
that would make it feasible to use DisposeAsync
in asynchronous scenarios.
await this.FlushAsynchronously() | ||
.FollowOnFaultWith(t => this.SetState(BatchWriterState.Error)) | ||
.ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does FollowOnFaultWith
do? Could this be safely transformed to something like:
try {
await this.FlushAsynchronously();
} catch {
this.SetState(BatchWriterState.Error);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FollowOnFaultWith
is designed such that the Action
delegate is invoked only when the antecedent task fails (i.e. task is in a Faulted state)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be transformed the way you demonstrated. But instead of littering the code with such try...catch I think it's neater to encapsulate that in a helper method
653a8f7
to
83c24dd
Compare
This PR has Quantification details
Why proper sizing of changes matters
Optimal pull request sizes drive a better predictable PR flow as they strike a
What can I do to optimize my changes
How to interpret the change counts in git diff output
Was this comment helpful? 👍 :ok_hand: :thumbsdown: (Email) |
Issues
This pull request is in partial fulfilment of issue #2019.
Description
Implement asynchronous support in
ODataJsonLightBatchWriter
To prevent a breaking change, I added the following 6
protected virtual
asynchronous methods to thepublic abstract ODataBatchWriter
class as asynchronous wrappers over the synchronous methods.WriteStartBatchImplementationAsync
WriteEndBatchImplementationAsync
WriteStartChangesetImplementationAsync
WriteEndChangesetImplementationAsync
CreateOperationRequestMessageImplementationAsync
CreateOperationResponseMessageImplementationAsync
These virtual asynchronous methods are then invoked from the public asynchronous methods. For example;
becomes
This I believe should guarantee asynchronous methods in the abstract base class will work as before unless overridden in a derived class.
It also allows asynchronous support to be implemented by overriding the protected methods in the derived class(es).
Checklist (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.