Skip to content

Commit

Permalink
Use ValueTask in FormReading
Browse files Browse the repository at this point in the history
Addressing Task allocations in  aspnet#553

Unfortunately this has a return type split between in public api net451
and dotnet5.4
  • Loading branch information
benaadams committed Feb 11, 2016
1 parent d3aff53 commit 0ee149c
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 29 deletions.
150 changes: 123 additions & 27 deletions src/Microsoft.AspNetCore.WebUtilities/FormReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,50 @@ public FormReader(Stream stream, Encoding encoding, ArrayPool<byte> bytePool, Ar
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>The next key value pair, or null when the end of the form is reached.</returns>
public async Task<KeyValuePair<string, string>?> ReadNextPairAsync(CancellationToken cancellationToken)
public ValueTask<KeyValuePair<string, string>?> ReadNextPairAsync(CancellationToken cancellationToken)
{
var key = await ReadWordAsync('=', cancellationToken);
var keyTask = ReadWordAsync('=', cancellationToken);
if (!keyTask.IsCompletedSuccessfully)
{
return ReadNextPairAsyncKeyAwaited(keyTask.AsTask(), cancellationToken);
}
var key = keyTask.Result;
if (string.IsNullOrEmpty(key) && _bufferCount == 0)
{
return (KeyValuePair<string, string>?)null;
}

var valueTask = ReadWordAsync('&', cancellationToken);
if (!valueTask.IsCompletedSuccessfully)
{
return ReadNextPairAsyncValueAwaited(key, valueTask.AsTask(), cancellationToken);
}

var value = valueTask.Result;
return new KeyValuePair<string, string>(key, value);
}

private async Task<KeyValuePair<string, string>?> ReadNextPairAsyncKeyAwaited(Task<string> keyTask, CancellationToken cancellationToken)
{
var key = await keyTask;
if (string.IsNullOrEmpty(key) && _bufferCount == 0)
{
return null;
}
var value = await ReadWordAsync('&', cancellationToken);

var valueTask = ReadWordAsync('&', cancellationToken);
if (!valueTask.IsCompletedSuccessfully)
{
var value = await valueTask;
return new KeyValuePair<string, string>(key, value);
}

return new KeyValuePair<string, string>(key, valueTask.Result);
}

private async Task<KeyValuePair<string, string>?> ReadNextPairAsyncValueAwaited(string key, Task<string> valueTask, CancellationToken cancellationToken)
{
var value = await valueTask;
return new KeyValuePair<string, string>(key, value);
}

Expand All @@ -112,24 +148,33 @@ private string ReadWord(char seperator)
Buffer();
}

// End
if (_bufferCount == 0)
var word = ReadWordImpl(seperator);
if (word != null)
{
return BuildWord();
return word;
}
}
}

var c = _buffer[_bufferOffset++];
_bufferCount--;
private ValueTask<string> ReadWordAsync(char seperator, CancellationToken cancellationToken)
{
// TODO: Configurable value size limit
while (true)
{
// Empty
if (_bufferCount == 0)
{
return ReadWordAsyncAwaited(seperator, cancellationToken);
}

if (c == seperator)
var word = ReadWordImpl(seperator);
if (word != null)
{
return BuildWord();
return word;
}
_builder.Append(c);
}
}

private async Task<string> ReadWordAsync(char seperator, CancellationToken cancellationToken)
private async Task<string> ReadWordAsyncAwaited(char seperator, CancellationToken cancellationToken)
{
// TODO: Configurable value size limit
while (true)
Expand All @@ -140,21 +185,32 @@ private async Task<string> ReadWordAsync(char seperator, CancellationToken cance
await BufferAsync(cancellationToken);
}

// End
if (_bufferCount == 0)
var word = ReadWordImpl(seperator);
if (word != null)
{
return BuildWord();
return word;
}
}
}

private string ReadWordImpl(char seperator)
{
// End
if (_bufferCount == 0)
{
return BuildWord();
}

var c = _buffer[_bufferOffset++];
_bufferCount--;
var c = _buffer[_bufferOffset++];
_bufferCount--;

if (c == seperator)
{
return BuildWord();
}
_builder.Append(c);
if (c == seperator)
{
return BuildWord();
}
_builder.Append(c);

return null;
}

// '+' un-escapes to ' ', %HH un-escapes as ASCII (or utf-8?)
Expand Down Expand Up @@ -209,7 +265,7 @@ public static Dictionary<string, StringValues> ReadForm(string text)
/// </summary>
/// <param name="stream">The HTTP form body to parse.</param>
/// <returns>The collection containing the parsed HTTP form body.</returns>
public static Task<Dictionary<string, StringValues>> ReadFormAsync(Stream stream, CancellationToken cancellationToken = new CancellationToken())
public static ValueTask<Dictionary<string, StringValues>> ReadFormAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
{
return ReadFormAsync(stream, Encoding.UTF8, cancellationToken);
}
Expand All @@ -219,12 +275,52 @@ public static Dictionary<string, StringValues> ReadForm(string text)
/// </summary>
/// <param name="stream">The HTTP form body to parse.</param>
/// <returns>The collection containing the parsed HTTP form body.</returns>
public static async Task<Dictionary<string, StringValues>> ReadFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = new CancellationToken())
public static ValueTask<Dictionary<string, StringValues>> ReadFormAsync(Stream stream, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken))
{
using (var reader = new FormReader(stream, encoding))
var isAsync = false;
var reader = new FormReader(stream, encoding);
try
{
var accumulator = new KeyValueAccumulator();
var pair = await reader.ReadNextPairAsync(cancellationToken);

var pairTask = reader.ReadNextPairAsync(cancellationToken);
if (!pairTask.IsCompletedSuccessfully)
{
isAsync = true;
return ReadFormAsyncAwaited(pairTask.AsTask(), reader, accumulator, cancellationToken);
}

var pair = pairTask.Result;
while (pair.HasValue)
{
accumulator.Append(pair.Value.Key, pair.Value.Value);

pairTask = reader.ReadNextPairAsync(cancellationToken);
if (!pairTask.IsCompletedSuccessfully)
{
isAsync = true;
return ReadFormAsyncAwaited(pairTask.AsTask(), reader, accumulator, cancellationToken);
}

pair = pairTask.Result;
}

return accumulator.GetResults();
}
finally
{
if (!isAsync)
{
reader.Dispose();
}
}
}

private static async Task<Dictionary<string, StringValues>> ReadFormAsyncAwaited(Task<KeyValuePair<string, string>?> pairTask, FormReader reader, KeyValueAccumulator accumulator, CancellationToken cancellationToken)
{
using (reader)
{
var pair = await pairTask;
while (pair.HasValue)
{
accumulator.Append(pair.Value.Key, pair.Value.Value);
Expand Down
6 changes: 4 additions & 2 deletions src/Microsoft.AspNetCore.WebUtilities/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
"dependencies": {
"Microsoft.Extensions.Primitives": "1.0.0-*",
"System.Buffers": "4.0.0-*",
"System.Text.Encodings.Web": "4.0.0-*"
"System.Text.Encodings.Web": "4.0.0-*",
"System.Threading.Tasks.Extensions": "4.0.0-*"
},
"frameworks": {
"net451": {
"frameworkAssemblies": {
"System.Runtime": ""
"System.Runtime": "",
"System.Threading.Tasks": "4.0.0.0"
}
},
"dotnet5.4": {
Expand Down

0 comments on commit 0ee149c

Please sign in to comment.