-
-
Notifications
You must be signed in to change notification settings - Fork 889
Async APIs / Fake Async Codecs #1196
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1196 +/- ##
==========================================
- Coverage 82.74% 82.40% -0.35%
==========================================
Files 695 696 +1
Lines 30252 30553 +301
Branches 3429 3482 +53
==========================================
+ Hits 25033 25176 +143
- Misses 4523 4658 +135
- Partials 696 719 +23
Continue to review full report at Codecov.
|
| } | ||
| else | ||
| { | ||
| // cheat for now do async copy of the stream into memory stream and use the sync version |
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.
I use a pool backed memory stream that allocates small chunks in .Web. Not sure how performant reading would be though.
https://github.com/SixLabors/ImageSharp.Web/blob/master/src/ImageSharp.Web/ChunkedMemoryStream.cs
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.
Is maybe RecyclableMemoryStream an option? Its meant to be a drop in replacement for MemoryStream
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.
I want to have a look at a read-only version of this less overhead and should support all out target frameworks.
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.
RecyclableMemoryStream seems more sophisticated at first glance.
Whichever we choose, I think it would make sense to integrate our adapted solution with MemoryAllocator.
|
@tocsoft I'll review this over the weekend. |
|
I will also try my best to have a look, but I need a couple of days, hope to make it before Saturday. |
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Co-authored-by: Brian Popow <38701097+brianpopow@users.noreply.github.com>
|
Making |
antonfirsov
left a comment
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.
Sorry for joining the party late. Mostly comments / discussion starters, the only real concern is the one about the FormattedImage type.
src/ImageSharp/Image.FromStream.cs
Outdated
| /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> | ||
| /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> | ||
| /// <returns>A <see cref="Task{FormattedImage}"/> representing the asynchronous operation.</returns> | ||
| public static Task<FormattedImage> LoadWithFormatAsync(Stream stream) |
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.
Wondering whether it would be better to have this family of methods under FormattedImage and FormattedImageInfo. Cleaner naming, but would be worse for discoverability.
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.
Structurally cleaner yeah, but I think we'll end up confusing people if we split out the async API. I'd rather avoid the questions.
src/ImageSharp/FormattedImage.cs
Outdated
| /// <summary> | ||
| /// Struct to curry <see cref="Image"/> and <see cref="IImageFormat"/> for return from async overloads. | ||
| /// </summary> | ||
| public readonly struct FormattedImage : IEquatable<FormattedImage> |
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 is the benefit for defining this type as a struct instead of a class? (Performance doesn't matter here.)
Is it future proof in it's current form? We may want to extend it to contain more more stuff.
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.
I'm not happy with the name just now... Needs some thought
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.
I've been putting some thought to this; I really don't want it to be an extensible object.
My reasoning is that we are merely replicating the synchronous API here.
I'm still not happy with the name Formatted suggests the image data is encoded. Do we even need our own types? We could use a named Tuple return type?
public static Task<(Image image, IImageFormat Format)> LoadWithFormatAsync (...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.
I think this also means we can keep the existing synchronous out IImageFormat APIs. It becomes more obvious with a tuple we are working around the syntatic limitations of the language.
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.
Named Tuples require a minimum language versions though (I'm assuming @tocsoft that's why you went the explicit struct route). Do we want to enforce that?
Dammit this is tricky.
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.
To be honest I totally didn't think about named tuples having a min version.
The reason I made an explicit type was because I was working on thought that you shouldn't return Tuples from public APIs, but on this sort of return site (working around language constructs) in hind sight I'm now less bothered in this instance. Even for older language versions just the types of the 2 properties are unique enough to distinguish what the items are.
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.
Thanks! I think I’m gonna push onwards with them then. The language constraints are low enough and they just feel right as an alternative to the sync API.
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.
If it's not an extensibility point, anonymous tuple looks better.
| } | ||
| else | ||
| { | ||
| using (var ms = new MemoryStream()) |
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.
In the future we may consider using our own variant of RecyclableMemoryStream to reduce allocation pressure.
Update: I joined the existing discussion.
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.
We don't even need one tbh. We know the length of the input stream, tightly control the lifecycle of the MemoryStream. We could simply rend an array from the shared pool and pass to the MemoryStream constructor.
| { | ||
| if (stream.CanSeek) | ||
| { | ||
| return this.Decode<TPixel>(stream); |
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.
I'm not sure whether this execution path is Stephen Toub proof, however I can live with it 😄
I have mixed feelings: the strict ASP.NET rules prohibiting sync code are unnecessary for CPU heavy imaging code path, but using async over sync is also far from being ideal. The picture is somewhat better if I view it as an implementation detail of decoders to be improved in a future by implementing actual async decoder path.
| Guard.NotNull(stream, nameof(stream)); | ||
|
|
||
| var decoder = new BmpDecoderCore(configuration, this); | ||
|
|
||
| try | ||
| { | ||
| return await decoder.DecodeAsync<TPixel>(stream).ConfigureAwait(false); | ||
| } | ||
| catch (InvalidMemoryOperationException ex) | ||
| { | ||
| Size dims = decoder.Dimensions; | ||
|
|
||
| throw new InvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); | ||
| } |
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.
We can DRY away these code duplications with the help of an IImageDecoderCore interface if we want. (Same for encoders.)
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.
Yeah, I like this. Can come later though since everything is internal (unless someone is desperately keen to do it).
|
I need to pick this up as it's likely beneficial to #1205 |
Prerequisites
Description
This adds the API surface to support async saving and loading from streams.
This is really a bit of a cheat and doesn't actually process the stream asynchronously just helps prevent people, when loading from aspnetcore setting where syncIO is disabled, having to manually do the memory copy we keep tellign them to do.. we just do it for you if we believe it will be needed and allows us (in the future) to start actually implement the codecs asynchronously as and when we want.
This API addition does raise the whether we need/want to introduce sync
XXXWithFormat()apis to match the async ones and depricate/hide the overloads withoutthe params to keep the sync and async APIs in sync.Also should the
FormattedImagestructs be madeIDisposableand pass the dispose along to theImageso you can dovs
or do we use some other way of representing those sorts of APIs?