C# Async await in a function called without await [duplicate] - c#

This question already has answers here:
Running multiple async tasks and waiting for them all to complete
(10 answers)
Closed 3 years ago.
I have a code to call a function in a loop, which would access endpoints asynchronously, coded like this:
public async Task HitEndPointsAsync()
{
for (int i = 0; i < _urls.Length; i++)
{
await HitOneEndPointAsync(i);
}
}
The simplified function HitOneEndPointAsync looks like this:
private async Task HitOneEndPointAsync(int i)
{
var newRec = InsertRec(i);
ExtURL extUrl = new ExtURL(_urls[i]);
result = await extUrl.GetAsync(_parms[i]);
UpdateRec(i, result);
}
If I remove the await in HitEndPointsAsync, then the await that is in HitOneEndPointAsync is no longer effective. If I need all endpoints to be called at once, but on each call await for the response to process that response further, would this be an option? Because as soon as I remove the await at the function call level, the await down the line is ignored. Thoughts?

You could try something like this if you don't want to await within the loop:
public async Task HitEndPointsAsync()
{
var tasks = new List<Task>();
for (int i = 0; i < _urls.Length; i++)
{
tasks.Add(HitOneEndPointsAsync(i));
}
await Task.WhenAll(tasks);
}
Note that this may add extra threads due to the calls to HitEndPointAsync being wrapped in a Task so I am not sure it has any advantages over the original version, maybe you could add more context to your question to know exactly what you are trying to do.

Related

Why using Task to load multiple pages' content length gives me the same result? [duplicate]

This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed 1 year ago.
I am trying to get the content length of multiple pages using Task in .NET Core asynchronously:
public static async Task GetContentLengthAsync(string url)
{
HttpClient httpClient = new HttpClient();
var httpResponse = await httpClient.GetAsync(url);
var page = await httpResponse.Content.ReadAsStringAsync();
Console.WriteLine(page.Length);
}
If I use it like this to take the first 100 pages' content length:
for (int i = 1; i <= 100; i++)
{
Task.Run(() => GetContentLengthAsync($"https://www.website.com/view/{i}"));
}
=> all the outputs are the same or different but incorrect. (but I get the results very fast)
If I run the Task with await like this by just calling the GetLengthsAsync():
public static async void GetLengthsAsync()
{
for (int i = 1; i <= 100; i++)
{
await Task.Run(() => GetContentLengthAsync($"https://www.website.com/view/{i}"));
}
}
=> the output is correct and I can still type in the console and do other tasks in that time but each of the GetContentLengthAsync tasks waits the other to be completed and uses only one thread at a time. Is there a way to make it run not only asynchronously but also on multiple threads at the same time without losing information?
P.S. I want to use Tasks because it's an university project and I know that there probably are better ways of doing it(but those are the requirements). It's more of a problem solving task to better understand how Task works.
A lambda function in a for() loop will 'capture' the loop variable. Most requests would have been for /view/100.
for (int i = 1; i <= 100; i++)
{
int localCopy = i;
Task.Run(() => GetContentLengthAsync($"https://www.website.com/view/{localCopy}"));
}

async/await not returning in caller method [duplicate]

This question already has answers here:
'await' works, but calling task.Result hangs/deadlocks
(6 answers)
Closed 3 years ago.
I am following this to get async/await working.
But when I run the below code as debug , it only prints
HandleFile enter
wait...
and then keeps on running , doesn't do anything, it seems to me HandleFileAsync never returns
public async Task method1()
{
Task<int> task = HandleFileAsync();
log.Info("wait...");
task.Wait();
var x = task.Result;
log.Info("print x.."+ x);
}
static async Task<int> HandleFileAsync()
{
string file = #"C:\Users\..\..\text.txt";
log.Info("HandleFile enter");
int count = 0;
// Read in the specified file.
// ... Use async StreamReader method.
using (StreamReader reader = new StreamReader(file))
{
string v = await reader.ReadToEndAsync();
// ... Process the file data somehow.
count += v.Length;
// ... A slow-running computation.
// Dummy code.
for (int i = 0; i < 1000; i++)
{
int x = v.GetHashCode();
if (x == 0)
{
count--;
}
}
}
log.Info("HandleFile exit");
return count;
}
How do I make it run to print x?
Looking at the methods you shared, the async/await looks a little awkward.
deleted the 1st note and changed the void back to Task. Since you are learning, these things are important.
2nd remove the task.wait command, use await. see the changes I have made below.
public async Task method1()
{
log.info('Starting process');
int task = await HandleFileAsync();
log.Info("print x.."+ task);
}
I had ran into the following Getting Started with Async / Await. The blog post is related to Xamarin, but it explain the async/await pattern really well. I hope it helps.

Await async call in for-each-loop [duplicate]

This question already has answers here:
Using async/await for multiple tasks
(8 answers)
Closed 4 years ago.
I have a method in which I'm retrieving a list of deployments. For each deployment I want to retrieve an associated release. Because all calls are made to an external API, I now have a foreach-loop in which those calls are made.
public static async Task<List<Deployment>> GetDeployments()
{
try
{
var depjson = await GetJson($"{BASEURL}release/deployments?deploymentStatus=succeeded&definitionId=2&definitionEnvironmentId=5&minStartedTime={MinDateTime}");
var deployments = (JsonConvert.DeserializeObject<DeploymentWrapper>(depjson))?.Value?.OrderByDescending(x => x.DeployedOn)?.ToList();
foreach (var deployment in deployments)
{
var reljson = await GetJson($"{BASEURL}release/releases/{deployment.ReleaseId}");
deployment.Release = JsonConvert.DeserializeObject<Release>(reljson);
}
return deployments;
}
catch (Exception)
{
throw;
}
}
This all works perfectly fine. However, I do not like the await in the foreach-loop at all. I also believe this is not considered good practice. I just don't see how to refactor this so the calls are made parallel, because the result of each call is used to set a property of the deployment.
I would appreciate any suggestions on how to make this method faster and, whenever possible, avoid the await-ing in the foreach-loop.
There is nothing wrong with what you are doing now. But there is a way to call all tasks at once instead of waiting for a single task, then processing it and then waiting for another one.
This is how you can turn this:
wait for one -> process -> wait for one -> process ...
into
wait for all -> process -> done
Convert this:
foreach (var deployment in deployments)
{
var reljson = await GetJson($"{BASEURL}release/releases/{deployment.ReleaseId}");
deployment.Release = JsonConvert.DeserializeObject<Release>(reljson);
}
To:
var deplTasks = deployments.Select(d => GetJson($"{BASEURL}release/releases/{d.ReleaseId}"));
var reljsons = await Task.WhenAll(deplTasks);
for(var index = 0; index < deployments.Count; index++)
{
deployments[index].Release = JsonConvert.DeserializeObject<Release>(reljsons[index]);
}
First you take a list of unfinished tasks. Then you await it and you get a collection of results (reljson's). Then you have to deserialize them and assign to Release.
By using await Task.WhenAll() you wait for all the tasks at the same time, so you should see a performance boost from that.
Let me know if there are typos, I didn't compile this code.
Fcin suggested to start all Tasks, await for them all to finish and then start deserializing the fetched data.
However, if the first Task is already finished, but the second task not, and internally the second task is awaiting, the first task could already start deserializing. This would shorten the time that your process is idly waiting.
So instead of:
var deplTasks = deployments.Select(d => GetJson($"{BASEURL}release/releases/{d.ReleaseId}"));
var reljsons = await Task.WhenAll(deplTasks);
for(var index = 0; index < deployments.Count; index++)
{
deployments[index].Release = JsonConvert.DeserializeObject<Release>(reljsons[index]);
}
I'd suggest the following slight change:
// async fetch the Release data of Deployment:
private async Task<Release> FetchReleaseDataAsync(Deployment deployment)
{
var reljson = await GetJson($"{BASEURL}release/releases/{deployment.ReleaseId}");
return JsonConvert.DeserializeObject<Release>(reljson);
}
// async fill the Release data of Deployment:
private async Task FillReleaseDataAsync(Deployment deployment)
{
deployment.Release = await FetchReleaseDataAsync(deployment);
}
Then your procedure is similar to the solution that Fcin suggested:
IEnumerable<Task> tasksFillDeploymentWithReleaseData = deployments.
.Select(deployment => FillReleaseDataAsync(deployment)
.ToList();
await Task.WhenAll(tasksFillDeploymentWithReleaseData);
Now if the first task has to wait while fetching the release data, the 2nd task begins and the third etc. If the first task already finished fetching the release data, but the other tasks are awaiting for their release data, the first task starts already deserializing it and assigns the result to deployment.Release, after which the first task is complete.
If for instance the 7th task got its data, but the 2nd task is still waiting, the 7th task can deserialize and assign the data to deployment.Release. Task 7 is completed.
This continues until all tasks are completed. Using this method there is less waiting time because as soon as one task has its data it is scheduled to start deserializing
If i understand you right and you want to make the var reljson = await GetJson parralel:
Try this:
Parallel.ForEach(deployments, (deployment) =>
{
var reljson = await GetJson($"{BASEURL}release/releases/{deployment.ReleaseId}");
deployment.Release = JsonConvert.DeserializeObject<Release>(reljson);
});
you might limit the number of parallel executions such as:
Parallel.ForEach(
deployments,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
(deployment) =>
{
var reljson = await GetJson($"{BASEURL}release/releases/{deployment.ReleaseId}");
deployment.Release = JsonConvert.DeserializeObject<Release>(reljson);
});
you might also want to be able to break the loop:
Parallel.ForEach(deployments, (deployment, state) =>
{
var reljson = await GetJson($"{BASEURL}release/releases/{deployment.ReleaseId}");
deployment.Release = JsonConvert.DeserializeObject<Release>(reljson);
if (noFurtherProcessingRequired) state.Break();
});

C# async and await keywords not working as expected with property getter [duplicate]

This question already has answers here:
Async/await and parallel in C# [closed]
(1 answer)
Run two async tasks in parallel and collect results in .NET 4.5
(6 answers)
Closed 5 years ago.
Can you briefly explain me why the first two rows of this code are not running parallel? How could I make it work paralell?
SensorLeft and SensorRight are of the same class, and Distance is a public property of it which needs some time to be calculated when calling its get method.
What am I doing wrong? Should I make the Distance calculation as an async function instead to be right?
public async void GetDistance()
{
await Task.Run(() => LeftDistance = SensorLeft.Distance);
await Task.Run(() => RightDistance = SensorRight.Distance);
Distance = RightDistance < LeftDistance ? RightDistance:LeftDistance;
}
When the compiler encounters await keyword it will automatically schedule a task in task scheduler. The operation waits (in a non-blocking manner) for the task to complete before continue to do the rest of the code block.
To make your code run in parallel you need to modify it into
public async Task GetDistance()
{
var leftDTask = Task.Run(() => LeftDistance = SensorLeft.Distance);
var rightDTask= Task.Run(() => RightDistance = SensorRight.Distance);
await Task.WhenAll(leftDTask,rightDTask);
Distance = RightDistance < LeftDistance ? RightDistance:LeftDistance;
}
Task.WhenAll has an overload to return Task<TResult[]> Task.WhenAll<TResult[]>. and you can replace your code into
public async Task GetDistance()
{
var leftDTask = Task.Run(() => SensorLeft.Distance);
var rightDTask= Task.Run(() => SensorRight.Distance);
var results= await Task.WhenAll(leftDTask,rightDTask);
Distance = Math.Min(results[0],results[1]);
}
However, this is not a good solution. If you want the get distance truly asynchronous, you should make an asynchronous get method rather wrap it into Task.Run.

Adding List of tasks without executing

I have a method which returns a task, which I want to call multiple times and wait for any 1 of them to be successful. The issue I am facing is as soon as I add the task to the List, it executes and since I added delay to simulate the work, it just block there.
Is there a way to add the the tasks to the list without really executing it and let whenAny execute the tasks.
The below code is from Linqpad editor.
async void Main()
{
var t = new Test();
List<Task<int>> tasks = new List<Task<int>>();
for( int i =0; i < 5; i++)
{
tasks.Add(t.Getdata());
}
var result = await Task.WhenAny(tasks);
result.Dump();
}
public class Test
{
public Task<int> Getdata()
{
"In Getdata method".Dump();
Task.Delay(90000).Wait();
return Task.FromResult(10);
}
}
Update :: Below one makes it clear, I was under the impression that if GetData makes a call to network it will get blocked during the time it actually completes.
async void Main()
{
OverNetwork t = new OverNetwork();
List<Task<string>> websitesContentTask = new List<Task<string>>();
websitesContentTask.Add(t.GetData("http://www.linqpad.net"));
websitesContentTask.Add(t.GetData("http://csharpindepth.com"));
websitesContentTask.Add(t.GetData("http://www.albahari.com/nutshell/"));
Task<string> completedTask = await Task.WhenAny(websitesContentTask);
string output = await completedTask;
Console.WriteLine(output);
}
public class OverNetwork
{
private HttpClient client = new HttpClient();
public Task<string> GetData(string uri)
{
return client.GetStringAsync(uri);
}
}
since I added delay to simulate the work, it just block there
Actually, your problem is that your code is calling Wait, which blocks the current thread until the delay is completed.
To properly use Task.Delay, you should use await:
public async Task<int> Getdata()
{
"In Getdata method".Dump();
await Task.Delay(90000);
return 10;
}
Is there a way to add the the tasks to the list without really executing it and let whenAny execute the tasks.
No. WhenAny never executes tasks. Ever.
It's possible to build a list of asynchronous delegates, i.e., a List<Func<Task>> and execute them later, but I don't think that's what you're really looking for.
There are multiple tasks in your Getdata method. First does delay, but you are returning finished task which returned 10. Try to change your code like this
return Task.Delay(90000).ContinueWith(t => 10)

Categories

Resources