简体   繁体   中英

Parallel.ForEachAsync Task vs ValueTask

I'm trying out Parallel.ForEachAsync and the compiler is kind enough to inform me that the body is a func that returns a ValueTask , not a Task .

Stopwatch sw = Stopwatch.StartNew();

var numbers = Enumerable.Range(start: 0, count: 10);

// Error: 'Task WorkAsync(int, CancellationToken)' has the wrong return type
await Parallel.ForEachAsync(
    source: numbers,
    parallelOptions: new ParallelOptions{ MaxDegreeOfParallelism = 2 },
    body: WorkAsync); 

async Task WorkAsync(int item, CancellationToken cancellationToken)
{
    WriteLine($"Task {item} Start");
    await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken).ConfigureAwait(false);
    WriteLine($"Task {item} End");
}

void WriteLine(string s) => Console.WriteLine($"{sw.ElapsedMilliseconds, 3} Thread{Thread.CurrentThread.ManagedThreadId}: {s}");

A quick search yielded only ForEachAsync examples that use a lambda with multiple statements: async (x, ct) => { ...; await ...; } async (x, ct) => { ...; await ...; } async (x, ct) => { ...; await ...; } .

I feel that

body: async (item, cancellationToken) => await WorkAsync(item, cancellationToken).ConfigureAwait(false));

is significantly uglier than:

body: WorkAsync);

In the trivial example here I can obviously change the return type of the worker method, but it seems unwise to modify existing code to return ValueTask s.

Is there an obvious solution I have missed for creating beautiful code with Parallel.ForEachAsync ?

I don't know if it qualifies as prettier, but you could consider doing this:

await Parallel.ForEachAsync(
    source: numbers,
    parallelOptions: new ParallelOptions { MaxDegreeOfParallelism = 2 },
    body: ToValueTaskResult<int>(WorkAsync));

static Func<T, CancellationToken, ValueTask> ToValueTaskResult<T>(
    Func<T, CancellationToken, Task> function)
        => async (item, ct) => await function(item, ct).ConfigureAwait(false);

To be honest I think that the Parallel.ForEachAsync API would be better if it accepted a Func<T, CancellationToken, Task> delegate as the body , since the most common usage scenarios for this API are high latency I/O-bound operations over the network. For these scenarios, switching from Task to ValueTask makes practically zero difference, in either performance or memory-efficiency. Reducing the general usability of the API in order to optimize some exotic scenarios with IValueTaskSource -backed body delegates, makes little sense to me. But this is what we have now, and it's certainly much prettier than what we had earlier , so I am not complaining too much. 😃

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM