❔ Any way to optimize this? Task.WhenAll

This takes anywhere from 6-14 seconds, Any way to make it faster?
private async Task HandlePhaseAudioFileStorage()
{
var tasks = new List<Task>();
foreach (var phase in _currentLesson.Phases)
{
// Generate default bot audio if not provided
if (phase.BotAudio is null)
{
phase.BotAudio = await _googleApiManager.TextToSpeechAsync(phase.BotText, _srcLanguageCode);
var defaultBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.DefaultPhaseBlobFileId = defaultBotBlobKey;
var botAudioWithTranslation = new AudioAndTranslation(phase.BotAudio, phase.BotText);
tasks.Add(_azureBlobStorageManager.SaveServerGeneratedFilesBytesAsync(defaultBotBlobKey,
botAudioWithTranslation));
}
if (phase.BotResponseTextOnSuccess is not null)
{
var successBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.SuccessPhaseBlobFileId = successBotBlobKey;

var botSuccessAudioTask = phase.BotResponseAudioOnSuccess is not null
? Task.FromResult(phase.BotResponseAudioOnSuccess)
: _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);

tasks.Add(botSuccessAudioTask.ContinueWith(async (botSuccessAudioTaskResult) =>
{
var botSuccessAudio = await botSuccessAudioTaskResult;
var botSuccessAudioWithTranslation =
new AudioAndTranslation(botSuccessAudio, phase.BotResponseTextOnSuccess);
await _azureBlobStorageManager.SaveServerGeneratedFilesBytesAsync(successBotBlobKey,
botSuccessAudioWithTranslation);
}));
}

}
}

await Task.WhenAll(tasks);
}
private async Task HandlePhaseAudioFileStorage()
{
var tasks = new List<Task>();
foreach (var phase in _currentLesson.Phases)
{
// Generate default bot audio if not provided
if (phase.BotAudio is null)
{
phase.BotAudio = await _googleApiManager.TextToSpeechAsync(phase.BotText, _srcLanguageCode);
var defaultBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.DefaultPhaseBlobFileId = defaultBotBlobKey;
var botAudioWithTranslation = new AudioAndTranslation(phase.BotAudio, phase.BotText);
tasks.Add(_azureBlobStorageManager.SaveServerGeneratedFilesBytesAsync(defaultBotBlobKey,
botAudioWithTranslation));
}
if (phase.BotResponseTextOnSuccess is not null)
{
var successBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.SuccessPhaseBlobFileId = successBotBlobKey;

var botSuccessAudioTask = phase.BotResponseAudioOnSuccess is not null
? Task.FromResult(phase.BotResponseAudioOnSuccess)
: _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);

tasks.Add(botSuccessAudioTask.ContinueWith(async (botSuccessAudioTaskResult) =>
{
var botSuccessAudio = await botSuccessAudioTaskResult;
var botSuccessAudioWithTranslation =
new AudioAndTranslation(botSuccessAudio, phase.BotResponseTextOnSuccess);
await _azureBlobStorageManager.SaveServerGeneratedFilesBytesAsync(successBotBlobKey,
botSuccessAudioWithTranslation);
}));
}

}
}

await Task.WhenAll(tasks);
}
71 Replies
Florian Voß
Florian Voß2y ago
not sure if my suggestions will make it faster but some things I would give a try @AntiMatter : - try rewriting your code to use Parallel.ForEach() - await only when the result of the async operation is needed. You are awaiting everything directly which is not always needed, e.g. you await TextToSpeechAsync() before you do other operations even tho you need the result (phase.BotAudio) after those other operations, you can await it later when you really need the result. - maybe you can make sync work that you have async too? such as GenerateBlobKey(), try making that async
JakenVeina
JakenVeina2y ago
private Task ProcessItemsInParallelAsync(IEnumerable<T> items)
=> Task.WhenAll(items
.Select(async item =>
{
...
}));
private Task ProcessItemsInParallelAsync(IEnumerable<T> items)
=> Task.WhenAll(items
.Select(async item =>
{
...
}));
if you're on .NET 6+, you also have access to Parallel.ForEachAsync() which gives you a few more under-the-hood mechanical optimizations, which might help if you have very large workloads
await Parallel.ForEachAsync(items,
async (item, cancellationToken) =>
{
...
});
await Parallel.ForEachAsync(items,
async (item, cancellationToken) =>
{
...
});
in your code, you're not actually running everything in parallel you've got an await in the first if block that forces some or all of those elements to serialize, instead of running in parallel
Florian Voß
Florian Voß2y ago
@AnievNekaj what do you think bout my suggestions, are they good? if we do all three things: 1. parallelize 2. await later when result is needed 3. make sync work async too it should get a lot faster right?
antimatter8189
antimatter8189OP2y ago
Well what i ended up doing is what @AnievNekaj suggested with await Parallel.ForEachAsync(_currentLesson.Phases) cut it down to 3-4 secs I cant paste the entire methode here basically :
var tasks = new List<Task>();
await Parallel.ForEachAsync(_currentLesson.Phases,
async (phase, cancellationToken) =>
{
// Generate default bot audio if not provided
if (phase.BotResponseTextOnSuccess is not null || phase.BotAudio is null)
{

if (phase.BotAudio is null)
{
var defaultBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.DefaultPhaseBlobFileId = defaultBotBlobKey;

tasks.Add(_googleApiManager.TextToSpeechAsync(phase.BotText, _srcLanguageCode).ContinueWith(
async (botAudioTaskResult) =>
{
phase.BotAudio = await botAudioTaskResult;
var botAudioWithTranslation = new AudioAndTranslation(phase.BotAudio, phase.BotText,
_currentLesson.Id, phase.PhaseNumber);
await _azureBlobStorageManager.SaveBotDefaultAudioContainer(defaultBotBlobKey,
botAudioWithTranslation);
}, cancellationToken));
}
}
var tasks = new List<Task>();
await Parallel.ForEachAsync(_currentLesson.Phases,
async (phase, cancellationToken) =>
{
// Generate default bot audio if not provided
if (phase.BotResponseTextOnSuccess is not null || phase.BotAudio is null)
{

if (phase.BotAudio is null)
{
var defaultBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.DefaultPhaseBlobFileId = defaultBotBlobKey;

tasks.Add(_googleApiManager.TextToSpeechAsync(phase.BotText, _srcLanguageCode).ContinueWith(
async (botAudioTaskResult) =>
{
phase.BotAudio = await botAudioTaskResult;
var botAudioWithTranslation = new AudioAndTranslation(phase.BotAudio, phase.BotText,
_currentLesson.Id, phase.PhaseNumber);
await _azureBlobStorageManager.SaveBotDefaultAudioContainer(defaultBotBlobKey,
botAudioWithTranslation);
}, cancellationToken));
}
}
@Florian Voß anything else to add to this? Im using .net 7 btw
Florian Voß
Florian Voß2y ago
looks much better now, you've fullfilled the first and second point I mentioned. Third is least important one, might potentially even make it slower
antimatter8189
antimatter8189OP2y ago
Thanks bud 😄 you too @AnievNekaj
becquerel
becquerel2y ago
you may also want to look into using ValueTask since there are branches in this where async work is not done (phase.BotAudio is not null and phase.BotResponseTextOnSuccess is null) if the non-async path is the most common one then ValueTask will let it run synchronously, which can be a perf boost valuetask comes with a lot of gotchas though, and i don't remember even half of them, so read the docs before trying it
JakenVeina
JakenVeina2y ago
Parallel.ForEachAsync() only supports ValueTask. And ValueTask is really only valuable for work that has a high likelihood of completing synchronously. This appears to be I/O work, which has no likelihood of completing synchronously.
antimatter8189
antimatter8189OP2y ago
I dont quit get that, You're saying Parallel.ForEachAsync() only supports ValueTask and thats no good for I/O work, but thats what i've implemented and it did cut down time
JakenVeina
JakenVeina2y ago
nono, I mean the advantage of using ValueTask over Task is when the majority of them will be already-complete upon creation, I.E. you ended up not having any asynchronous work to do. In this scenario, ValueTask avoids all the memory allocations that are associated with using a Task, regardless of whether it completes immediately. You're seeing benefit because you weren't properly parallelizing before, not because you're using ValueTasks. Which you might not be using, anyway. I'm not sure what the rules are for anonymous async methods, regarding when they return ValueTask instead of Task, or if that's even a thing at all. I believe Task can be implicitly cast to ValueTask, so just because Parallel.ForEachAsync() only accepts ValueTask, that doesn't necessarily mean that your async method is returning one.
antimatter8189
antimatter8189OP2y ago
Gotcha another small question
async (phase, cancellationToken) =>
async (phase, cancellationToken) =>
Tells me its async void basically cuz no await inside any way to mitigate it? @AnievNekaj the await only happens in certain scopes
JakenVeina
JakenVeina2y ago
if there's no await in the method, don't make it async
antimatter8189
antimatter8189OP2y ago
yeah then i cant use ContinueWith in the inner IF scopes, lol odd stuff Is this warning even solvable
JakenVeina
JakenVeina2y ago
incorrect also, there is no reason for you to be using .ContinueWith()
antimatter8189
antimatter8189OP2y ago
How so?
if (phase.BotResponseTextOnSuccess is not null)
{
var successBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.SuccessPhaseBlobFileId = successBotBlobKey;

var botSuccessAudioTask = phase.BotResponseAudioOnSuccess is not null
? Task.FromResult(phase.BotResponseAudioOnSuccess)
: _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);

tasks.Add(botSuccessAudioTask.ContinueWith(async (botSuccessAudioTaskResult) =>
{
var botSuccessAudio = await botSuccessAudioTaskResult;
var botSuccessAudioWithTranslation =
new AudioAndTranslation(botSuccessAudio, phase.BotResponseTextOnSuccess, _currentLesson.Id,
phase.PhaseNumber);
await _azureBlobStorageManager.SaveBotSuccessContainer(successBotBlobKey,
botSuccessAudioWithTranslation);
}, cancellationToken));
}
if (phase.BotResponseTextOnSuccess is not null)
{
var successBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.SuccessPhaseBlobFileId = successBotBlobKey;

var botSuccessAudioTask = phase.BotResponseAudioOnSuccess is not null
? Task.FromResult(phase.BotResponseAudioOnSuccess)
: _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);

tasks.Add(botSuccessAudioTask.ContinueWith(async (botSuccessAudioTaskResult) =>
{
var botSuccessAudio = await botSuccessAudioTaskResult;
var botSuccessAudioWithTranslation =
new AudioAndTranslation(botSuccessAudio, phase.BotResponseTextOnSuccess, _currentLesson.Id,
phase.PhaseNumber);
await _azureBlobStorageManager.SaveBotSuccessContainer(successBotBlobKey,
botSuccessAudioWithTranslation);
}, cancellationToken));
}
I need the bot audio to save to the blob storage I need the api call to finish before i pass it to be saved
JakenVeina
JakenVeina2y ago
because that's what await is for
antimatter8189
antimatter8189OP2y ago
I do have this
var botSuccessAudio = await botSuccessAudioTaskResult;
var botSuccessAudio = await botSuccessAudioTaskResult;
JakenVeina
JakenVeina2y ago
right do that
antimatter8189
antimatter8189OP2y ago
hm? this is the first time im using continueWith btw
JakenVeina
JakenVeina2y ago
and the last
antimatter8189
antimatter8189OP2y ago
Can u elaborate or provide some small code so i can undestand what im doing wrong
JakenVeina
JakenVeina2y ago
unless you are a framework author, you have no reason to use .ContinueWith() what is the async work you need to do
antimatter8189
antimatter8189OP2y ago
Well get audio from an api and save it to the blob depending on if text is null
JakenVeina
JakenVeina2y ago
okay, the first part of that what is the code to do that?
gerard
gerard2y ago
.ContinueWith is from .NET Framework 4.0, when async/await wasn't a thing Sweats
antimatter8189
antimatter8189OP2y ago
ehh its hard with partial code
antimatter8189
antimatter8189OP2y ago
Pastebin
private async Task HandlePhaseAudioFileStorage() { var t...
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
JakenVeina
JakenVeina2y ago
and it is, quite literally, what await does, just cleaner
antimatter8189
antimatter8189OP2y ago
I want to optimize this as much as i can cuz its a choke point atm
JakenVeina
JakenVeina2y ago
yeah, I was unable to read your snippets on mpbipe already *mobile
antimatter8189
antimatter8189OP2y ago
This is the full methode not that long about 60 lines anways Wtf do i need to do, give me a specific step or w/e lol
JakenVeina
JakenVeina2y ago
you said it yourself, you need to retrieve audio from an API
antimatter8189
antimatter8189OP2y ago
Lol oh rly
JakenVeina
JakenVeina2y ago
var audio = await Api.GetAudioAsync();
var audio = await Api.GetAudioAsync();
then save it to a blob
await blob.SaveAudioAsync(audio);
await blob.SaveAudioAsync(audio);
and you had some kinda condition for doing this
gerard
gerard2y ago
And remove the tasks list. You're currently adding everything to a list in Parallel.ForEachAsync without awaiting anything. This defeats the purpose of this method.
JakenVeina
JakenVeina2y ago
if (condition)
{
var audio = await Api.GetAudioAsync();
await blob.SaveAudioAsync(audio);
}
if (condition)
{
var audio = await Api.GetAudioAsync();
await blob.SaveAudioAsync(audio);
}
gerard
gerard2y ago
Either use Task.WhenAll or Parallel.ForEachAsync
antimatter8189
antimatter8189OP2y ago
so
JakenVeina
JakenVeina2y ago
yes, we've already been over that
antimatter8189
antimatter8189OP2y ago
smth like this
// Generate bot response audio on success if provided
if (phase.BotResponseTextOnSuccess is not null)
{
var successBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.SuccessPhaseBlobFileId = successBotBlobKey;

var botSuccessAudioTaskResult = phase.BotResponseAudioOnSuccess is not null
? phase.BotResponseAudioOnSuccess
: await _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);

var botSuccessAudioWithTranslation =
new AudioAndTranslation(botSuccessAudioTaskResult, phase.BotResponseTextOnSuccess, _currentLesson.Id,
phase.PhaseNumber);
tasks.Add(_azureBlobStorageManager.SaveBotSuccessContainer(successBotBlobKey,
botSuccessAudioWithTranslation));
}
// Generate bot response audio on success if provided
if (phase.BotResponseTextOnSuccess is not null)
{
var successBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.SuccessPhaseBlobFileId = successBotBlobKey;

var botSuccessAudioTaskResult = phase.BotResponseAudioOnSuccess is not null
? phase.BotResponseAudioOnSuccess
: await _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);

var botSuccessAudioWithTranslation =
new AudioAndTranslation(botSuccessAudioTaskResult, phase.BotResponseTextOnSuccess, _currentLesson.Id,
phase.PhaseNumber);
tasks.Add(_azureBlobStorageManager.SaveBotSuccessContainer(successBotBlobKey,
botSuccessAudioWithTranslation));
}
JakenVeina
JakenVeina2y ago
going back to my original example
antimatter8189
antimatter8189OP2y ago
Yey we got there bois
JakenVeina
JakenVeina2y ago
await Parallel.ForEachAsync(items,
async (item, cancellationToken) =>
{
...
});
await Parallel.ForEachAsync(items,
async (item, cancellationToken) =>
{
...
});
antimatter8189
antimatter8189OP2y ago
hm yeah i remember that one
JakenVeina
JakenVeina2y ago
whatever async work you need to do for each item goes where the ... is just write declarative code like you normally would except, anything that returns a Task, you await that's it
antimatter8189
antimatter8189OP2y ago
Gotcha bud, thx alot!
JakenVeina
JakenVeina2y ago
if you want further context, write out this async method you need, with whatever awaits and if blocks and logic you need then compile it and go take a look at what the compiler generates, with ILSpy
antimatter8189
antimatter8189OP2y ago
well i got to this:
await Parallel.ForEachAsync(_currentLesson.Phases,
async (phase, cancellationToken) =>
{
async Task SaveDefaultBotAudio()
{
var defaultBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.DefaultPhaseBlobFileId = defaultBotBlobKey;

var botAudio = await _googleApiManager.TextToSpeechAsync(phase.BotText, _srcLanguageCode);
var botAudioWithTranslation = new AudioAndTranslation(botAudio, phase.BotText,
_currentLesson.Id, phase.PhaseNumber);

await _azureBlobStorageManager.SaveBotDefaultAudioContainer(defaultBotBlobKey,
botAudioWithTranslation);
}
await Parallel.ForEachAsync(_currentLesson.Phases,
async (phase, cancellationToken) =>
{
async Task SaveDefaultBotAudio()
{
var defaultBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.DefaultPhaseBlobFileId = defaultBotBlobKey;

var botAudio = await _googleApiManager.TextToSpeechAsync(phase.BotText, _srcLanguageCode);
var botAudioWithTranslation = new AudioAndTranslation(botAudio, phase.BotText,
_currentLesson.Id, phase.PhaseNumber);

await _azureBlobStorageManager.SaveBotDefaultAudioContainer(defaultBotBlobKey,
botAudioWithTranslation);
}
and each task like this i add to the list and execute them all at the end
JakenVeina
JakenVeina2y ago
alternatively, you can see decompilations with sharplab.io
antimatter8189
antimatter8189OP2y ago
Yeah ik them tools, i think its built in rider which im using
JakenVeina
JakenVeina2y ago
again, I really cannot read that on mobile is that a private function inside the anonymous delegate? SaveDefaultBotAudio()?
antimatter8189
antimatter8189OP2y ago
ahh shit smth broke i dont get the audio files back now
async Task SaveDefaultBotAudio()
async Task SaveDefaultBotAudio()
JakenVeina
JakenVeina2y ago
yeah
antimatter8189
antimatter8189OP2y ago
await Parallel.ForEachAsync(_currentLesson.Phases,
async (phase, cancellationToken) =>
{
async Task SaveDefaultBotAudio()
await Parallel.ForEachAsync(_currentLesson.Phases,
async (phase, cancellationToken) =>
{
async Task SaveDefaultBotAudio()
`
JakenVeina
JakenVeina2y ago
what's the purpose of that?
antimatter8189
antimatter8189OP2y ago
Idk i though it would be faster/efficient I guess i need to go read more about async/await I;ll be back here tomorrow with insights.
JakenVeina
JakenVeina2y ago
it would be faster to declare a reusable function that isn't reused anywhere else? or is it?
antimatter8189
antimatter8189OP2y ago
if (phase.BotResponseTextOnSuccess is not null)
{
var successBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.SuccessPhaseBlobFileId = successBotBlobKey;

var botSuccessAudioTaskResult = phase.BotResponseAudioOnSuccess is not null
? phase.BotResponseAudioOnSuccess
: await _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);

var botSuccessAudioWithTranslation =
new AudioAndTranslation(botSuccessAudioTaskResult, phase.BotResponseTextOnSuccess, _currentLesson.Id,
phase.PhaseNumber);
tasks.Add(_azureBlobStorageManager.SaveBotSuccessContainer(successBotBlobKey,
botSuccessAudioWithTranslation));
}
if (phase.BotResponseTextOnSuccess is not null)
{
var successBotBlobKey = _azureBlobStorageManager.GenerateBlobKey();
phase.SuccessPhaseBlobFileId = successBotBlobKey;

var botSuccessAudioTaskResult = phase.BotResponseAudioOnSuccess is not null
? phase.BotResponseAudioOnSuccess
: await _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);

var botSuccessAudioWithTranslation =
new AudioAndTranslation(botSuccessAudioTaskResult, phase.BotResponseTextOnSuccess, _currentLesson.Id,
phase.PhaseNumber);
tasks.Add(_azureBlobStorageManager.SaveBotSuccessContainer(successBotBlobKey,
botSuccessAudioWithTranslation));
}
This works and kinda is fast so gonna keep it like that for now the azure tasks get sent all together the other ones i await
JakenVeina
JakenVeina2y ago
they get sent all together?
antimatter8189
antimatter8189OP2y ago
Its only used once Well smth is working right since before it used to take 10-14 secs now its 4-6
JakenVeina
JakenVeina2y ago
yeah, the parallelism is probably the bulk of that you're now starting all of the google API calls at basically the same time and each time one of them returns, you immediately follow up with the next step (if any)
antimatter8189
antimatter8189OP2y ago
so each
: await _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);
: await _googleApiManager.TextToSpeechAsync(phase.BotResponseTextOnSuccess, _srcLanguageCode);
gets a new thread? and executes on it? then comes back to the azure blob save logic? thats the flow?
JakenVeina
JakenVeina2y ago
threads aren't even really a concern here maybe, maybe not, depends on how Parallel.ForEachAsync() schedules things
antimatter8189
antimatter8189OP2y ago
How does it work, it awaits the google api, then adds a task to do smth with the result no? then all them tasks in the list that are purely related to blobstorage get executed together
JakenVeina
JakenVeina2y ago
each execution of the async delegate is its own "logical" thread
antimatter8189
antimatter8189OP2y ago
but becuse of this
await Parallel.ForEachAsync(_currentLesson.Phases,
async (phase, cancellationToken) =>
await Parallel.ForEachAsync(_currentLesson.Phases,
async (phase, cancellationToken) =>
all the await tasks get sent together Im getting this correct?
JakenVeina
JakenVeina2y ago
yes, it terms of what you're trying to say, but no in terms of what's actually happening I'm happy to descibe what's going on in more detail, but I'm about to be on the road for 6 hours
antimatter8189
antimatter8189OP2y ago
I'll appreciate if when u come back you can dive in its interesting
JakenVeina
JakenVeina2y ago
you can also go read up on how async/await works, mechanically
antimatter8189
antimatter8189OP2y ago
Im going off for today also Ik it creates a state machine etc i saw the nick chapsas videos Anyways thx for today bud! gn and thanks again
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.

Did you find this page helpful?