Yet Another HttpClient / Async Deadlock - c#

I've been banging my head on this for hours, and I've thrown my hands up in the air on this one. As far as I can tell, I've encountered a deadlock surrounding HttpClient and Async.
The goal is for a series of unrelated Posts to all get fired off in rapid succession, wait for them all to get done, and then construct a document from the resultset. The program has a WPF UI, and this is triggered by button:
private async void Generate_Suite_BTN_Click(object sender, RoutedEventArgs e)
{
var suiteBuilder = new SuiteBuilder();
await Task.Run(() => suiteBuilder.worker_Run());
}
That triggers the worker_Run(), which has some switch logic, and eventually gets down to hitting SendFiles() which has a Parrallel.Foreach, since the sending of the files doesn't need to be sequential, and are unrelated to eachother:
private bool SendFiles()
{
var result = Parallel.ForEach(_InfoCollection, SendFile);
return result.IsCompleted;
}
Each of those (in the parallel), is awaited in SendFile(), which also has some switch logic, based on what we're sending, but ultimately boils down to:
var result = await Client.SendMessage ( vars );
results.Add(result.MessageId, result.StatusCode, result.HttpReason);
Here's the HttpClient portion in SendMessage():
public async Task<Result> SendMessage(vars)
{
var soapResponse = new XmlDocument();
try
{
Client.DefaultRequestHeaders.Add("SOAPAction", soapAction);
Client.Timeout = TimeSpan.FromSeconds(Timeout);
var content = new StringContent(soapRequest, Encoding.UTF8, contentType);
var post = await Client.PostAsync(url, content).ConfigureAwait(false);
var response = post.Content;
result.StatusCode = post.StatusCode;
result.HttpReason = post.ReasonPhrase;
var sResponse = await response.ReadAsStringAsync().ConfigureAwait(false);
soapResponse.Load(sResponse);
}
catch (Exception ex)
{
//Catch logic
}
}
I can see the request and response passing back and forth with Fiddler, but I'm having trouble line-by-line debugging, since as soon as I hit the PostAsync, VS flips out and continues all the way to the end of the program, skipping all breakpoints. Meanwhile, the request times out with a TaskCanceledException, long after the code that should come afterwards already has completed.
I've looked a dozens of question & answers here on SO, and elsewhere, but they're just not quite helping find the problem. Most seem to center around the liberal sprinkling of ".ConfigureAwait(false)" on Async calls, but it doesn't really seem to have any effect.

So, with the help of #JSteward in the comments, he pointed out that async and Parrallel.ForEach aren't well suited to working together, since return types of void when dealing with async should be avoided.
He recommended that I use Async only, from the top (Button Click) to the bottom (Message Send), and that worked out. Credit to him for the guidance.
This link was helpful in explaining why this was the case: Async/Await - Best Practices in Asynchronous Programming
SendFiles ended up looking something like this:
private async Task<bool> SendFiles()
{
var result = _InfoCollection.Select(SendFile);
await Task.WhenAll(result).ConfigureAwait(false);
return true;
}
All the other methods went async, with appropriate awaits, and return types of Task or Task<T>.

Related

WebAPI HTTP request not completing until queued work kicks off on background task

In my .Net 6 WebPI service, I am queueing work to a background task queue, very closely based on the example here, but I could post parts of my implementation if that would help:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio#queued-background-tasks
I am running into unexpected behavior where control is not returned to the caller, even after the return Ok(..) completes in the controller. Instead the request only completes after the await Task.Delay(1000); line is reached on the queued work item. The request returns to the client as soon as this line is reached, and does not need to wait for the Delay to finish.
I'm guessing this is because of the await either starting a new async context, or someone un-sticking the async context of the original request. My intention is for the request to complete immediately after queuing the work item.
Any insight into what is happening here would be greatly appreciated.
Controller:
public async Task<ActionResult> Test()
{
var resultMessage = await _testQueue.DoIt();
return Ok(new { Message = resultMessage });
}
Queueing Work:
public TestAdapter(ITaskQueue taskQueue)
{
_taskQueue = taskQueue;
}
public async Task<string> DoIt()
{
await _taskQueue.QueueBackgroundWorkItemAsync(async (_cancellationToken) =>
{
await Task.Delay(1000);
var y = 12;
});
return "cool";
}
IoC:
services.AddHostedService<QueueHostedService>();
services.AddSingleton<ITTaskQueue>(ctx =>
{
return new TaskQueue(MaxQueueCount);
});
TaskQueue:
private readonly Channel<BackgroundTaskContext> _queue;
public TaskQueue(int capacity)
{
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<BackgroundTaskContext>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(new BackgroundTaskContext(workItem, ...));
}
Not sure what you expect here. I'm assuming you want the async method to return the cool in the api response. That's fine but because your also awaiting the async call with in DoIt(), then it pauses until QueueBackgroundWorkItemAsync finishes. You could remove the await and it will run and return as you expect.
I can't say that I'm a big fan of that design as you lose contact with it with exception of the cancellation token. Another thought would be to Send that work off to a console job or function app using message bus or even another http call.
Additional Notes:
Async can be complicated to explain because in reality, it wraps up code and executes on a thread of it's choosing. The await simulates the synchronous behavior.
await Task.Delay(1000); // Runs on it's own thread but still halts code execution for 1 second.
await _taskQueue.QueueBackgroundWorkItemAsync(async (_cancellationToken) // Waits for control to be returned from the code inside.
var resultMessage = await _testQueue.DoIt(); // Always waits for the code inside to complete.
If your wanting something to run without pausing code execution, you can either remove the await or add a Task.Run(() => { }); pattern. Is it a good idea is a whole other question. It also matters whether you need information back from the async method. If you don't await it then you'll get null back as it doesn't wait around for the answer to be computed.
This appears just to have been user error using the debugger. The debugger is switching to the background task thread and hitting breakpoints there before the response fully returns giving the appearance that control was not being returned to the client and being carried into the background task.
Even after adding some synchronous steps in QueueBackgroundWorkItemAsync and putting breakpoints on them, control was not immediately returned to the original http call. Only after I tried adding a long running task like await Task.Delay(1000); did enough time/ticks pass for the http response to return. I had conflated this with just the await somehow freeing up the original http context.

Speed up multiple API calls

So I did this project in uni that I am trying to refactor. One of the problems I am having is my method for getting the top list which consist of around 250 movies, e.g. 250 API calls. After that I render them all on my web page. The API I am using is OMDBAPI and I am getting every movie individually as you can see in the code below.
Basically that the web page does is as default loads 10 movies but I can also load in all movies which is around 250.
I am trying to wrap my head around asynchronous programming. So basically it is taking around 4-6 seconds to process this method according to stopwatch in C# but I believe it should be possible to refactor and refine. I am new to asynchronous programming and I have tried looking at MSFT documentation and several issues before here on SO, but I am not getting anywhere with speeding up the calls.
I have looked at using parallel for the issue but I think my problem should be solvable with async?
With stopwatch in C# I have pinpointed the delay to come mostly from between the two x.
I would foremost like to speed up the calls but I would love tips on best practice with async programming as well.
public async Task<List<HomeTopListMovieDTO>> GetTopListAggregatedData(Parameter parameter)
{
List<Task<HomeTopListMovieDTO>> tasks = new List<Task<HomeTopListMovieDTO>>();
var toplist = await GetToplist(parameter);
//x
foreach (var movie in toplist)
{
tasks.Add(GetTopListMovieDetails(movie.ImdbID));
}
var results = Task.WhenAll(tasks);
//x
var tempToplist = toplist.ToArray();
for (int i = 0; i < tasks.Count; i++)
{
tasks[i].Result.NumberOfLikes = tempToplist[i].NumberOfLikes;
tasks[i].Result.NumberOfDislikes = tempToplist[i].NumberOfDislikes;
}
List<HomeTopListMovieDTO> toplistMovies = results.Result.ToList();
return toplistMovies;
}
public async Task<HomeTopListMovieDTO> GetTopListMovieDetails(string imdbId)
{
string urlString = baseUrl + "i=" + imdbId + accessKey;
return await apiWebClient.GetAsync<HomeTopListMovieDTO>(urlString);
}
public async Task<T> GetAsync<T>(string urlString)
{
using (HttpClient client = new HttpClient())
{
var response = await client.GetAsync(urlString,
HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<T>(data);
return result;
}
}
You async code looks OKey. I would throttle it to not make more than X parallel requests using Partitioner / Parallel for each instead but approach with WaitAll is also good enough unless you see connection refused because of port exhaustion or API DDOS protection.
You should reuse HttpClient, see more details in
https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong, so in your case create HttpClient in the root method and pass it as a parameter to your async methods. HttpClient is thread safe, can be used in parallel calls.
You should dispose HttpResponse.

Too many nested async methods. Is that a problem?

The following code gets a list of investments belonging to a customer from 3 different resources. The flow starts with a controller's call and follows the flow described below where all methods are declared as async and called with await operator.
I'm wondering if is there a problem making all methods as async. Is there any performance penalty? Is it a code smell or an anti-pattern?
I know there are things that must be waited like access url, get data from cahce, etc. But I think there are things like filling a list or sum some few values doesn't need to be async.
Below follow the code (some parts where ommited for clearness):
Controller
{HttpGet]
public async Task<IActionResult> Get()
{
Client client = await _mediator.Send(new RecuperarInvestimentosQuery());
return Ok(cliente);
}
QueryHandler
public async Task<Client> Handle(RecoverInvestimentsQuery request, CancellationToken cancellationToken)
{
Client client;
List<Investiment> list = await _investimentBuilder.GetInvestiments();
client = new Cliente(request.Id, list);
return client;
}
InvestmentBuilder
public async Task<List<Investiment>> GetInvestiments()
{
ListInvestiments builder = new ListInvestiments();
await builder.BuildLists(_builder);
// here I get the List<Investiment> list already fulfilled to return to the controller
return list;
}
BuildLists
public async Task BuildLists(IBuilder builder)
{
Task[] tasks = new Task[] {
builder.GetFundsAsync(), //****
builder.ObterTesouro(),
builder.ObterRendaFixa()
};
await Task.WhenAll(tasks);
}
Funds, Bonds and Fixed Income Services (***all 3 methods are equal, only its name vary, so I just put one of them for the sake of saving space)
public async Task GetFundsAsync()
{
var listOfFunds = await _FundsService.RecoverFundsAsync();
// listOfFunds will get all items from all types of investments
}
Recover Funds, Bonds and Fixed Incomes methods are equals too, again I just put one of them
public async Task<List<Funds>> RecoverFundsAsync()
{
var returnCache = await _clientCache.GetValueAsync("fundsService");
// if not in cache, so go get from url
if (returnCache == null)
{
string url = _configuration.GetValue<string>("Urls:Funds");
var response = await _clienteHttp.ObterDadosAsync(url);
if (response != null)
{
string funds = JObject.Parse(response).SelectToken("funds").ToString();
await _clienteCache.SetValueAsync("fundService", funds);
return JsonConvert.DeserializeObject<List<Funds>>(fundos);
}
else
return null;
}
return JsonConvert.DeserializeObject<List<Funds>>(returnCache);
}
HTTP Client
public async Task<string> GetDataAsync(string Url)
{
using (HttpClient client = _clientFactory.CreateClient())
{
var response = await client.GetAsync(Url);
if (response.IsSuccessStatusCode)
return await response.Content.ReadAsStringAsync();
else
return null;
}
}
Cache Client
public async Task<string> GetValueAsync(string key)
{
IDatabase cache = Connection.GetDatabase();
RedisValue value = await cache.StringGetAsync(key);
if (value.HasValue)
return value.ToString();
else
return null;
}
Could someone give a thought about that?
Thanks in advance.
Your code looks okay for me. You are using async and await just for I/O and web access operations, and it perfectly fits for async and await purposes:
For I/O-bound code, you await an operation that returns a Task or Task inside of an async method.
For CPU-bound code, you await an operation that is started on a background thread with the Task.Run method.
Once you've used async and await, then all pieces of your code tends to become asynchronous too. This fact is described greatly in the MSDN article - Async/Await - Best Practices in Asynchronous Programming:
Asynchronous code reminds me of the story of a fellow who mentioned
that the world was suspended in space and was immediately challenged
by an elderly lady claiming that the world rested on the back of a
giant turtle. When the man enquired what the turtle was standing on,
the lady replied, “You’re very clever, young man, but it’s turtles all
the way down!” As you convert synchronous code to asynchronous code,
you’ll find that it works best if asynchronous code calls and is
called by other asynchronous code—all the way down (or “up,” if you
prefer). Others have also noticed the spreading behavior of
asynchronous programming and have called it “contagious” or compared
it to a zombie virus. Whether turtles or zombies, it’s definitely true
that asynchronous code tends to drive surrounding code to also be
asynchronous. This behavior is inherent in all types of asynchronous
programming, not just the new async/await keywords.

How to use Task.WhenAny with ReadLineAsync to get data from any TcpClient

working my way through all that is async / await (coming from threadpools) and I've hit an interesting challenge.
I have a TCP Server running in a WPF application that accepts clients and stores them in a List<> like such:
private List<Client> clients = new List<Client>();
while (running && clientCount <= maxClients)
{
Client client = new Client(await server.AcceptTcpClientAsync());
await client.WriteLineAsync("Connected to the Server!");
clients.Add(client);
clientCount++;
}
So what I want to do is iterate through the list of my Clients and if any data is received, I want to append it to a textbox. I realize this may not be the best way to achieve this, and I'm open to suggestions, but this is how I currently have it structured.
A button starts the loop and continuously calls and awaits AllReadLineAsync()
private async void btnStartReadLoopClick(object sender, RoutedEventArgs e)
{
btnStartReadLoop.IsEnabled = false;
while(server.clientCount > 0)
{
string text = await server.AllReadLineAsync();
txtOutputLog.AppendText("[client] " + text + "\n");
}
}
which is this function:
public async Task<string> AllReadLineAsync()
{
var tasklist = new List<Task<string>>();
foreach (var client in clients)
tasklist.Add(client.ReadLineAsync());
while (tasklist.Count > 0)
{
Task<string> finishedTask = await Task.WhenAny(tasklist);
if (finishedTask.Status == TaskStatus.RanToCompletion)
return await finishedTask;
tasklist.Remove(finishedTask);
}
return "Error: No task finished";
}
This function iterates through the list of clients and creates a List<Tast<string>> of all the ReadLineAsync() tasks.
At any given time, I may only have 1 or 2 clients actually sending data, so I can't WhenAll() and I've tried WhenAny() and WaitAny() without success.
Note to future googlers: WaitAny() is like Wait() and is blocking. Do not do this on a UI thread. Instead use WhenAny() and await it.
So my current implementation kind of works, but I can't figure out this bug where messages will build up from 1 client if the other clients don't send data.
TL;DR: Am I using WhenAny() correctly or is there a better way for me to await ReadLineAsync and pass the result to a textbox?
EDIT: Here's the behavior I'm seeing
I typed in this order: Left, Right, Left 2, Right 2, Left 3, Right 3
and it appears as if some messages are being dropped?
EDIT 2: I found the source of the code snippet I copied on the MSDN blog: https://blogs.msdn.microsoft.com/pfxteam/2012/08/02/processing-tasks-as-they-complete/
This code snippet is meant specifically to iterate through a list of tasks ensuring they all are completed. I don't care if tasks are duplicated though so I need to modify the code to always check the entire tasklist instead of removing any tasks.
it appears as if some messages are being dropped?
Yes. Because asynchronous work is started when you call their method (e.g., ReadLineAsync). When one of them completes (Task.WhenAny), your code abandons the other tasks. But they keep running - they're still reading from their sockets, and whatever they read is just going to be thrown away.
AFAIK, the behavior is undefined when you start reading from the same socket again - it's possible it may read what's next, or it's possible it may be queued. I do know that you're not supposed to do issue multiple reads from a socket (or any stream) simultaneously.
Sockets are not a perfect match to async, because they can send data at any time. You should use Rx or events instead. It's possible to make async work but it's extremely complex.
Alright so I figured out where I was going wrong and why my previous code didn't work.
First off, let's talk about what this code does, and why it doesn't work:
public async Task<string> AllReadLineAsync()
{
var tasklist = new List<Task<string>>();
foreach (var client in clients)
tasklist.Add(client.ReadLineAsync());
while (tasklist.Count > 0)
{
Task<string> finishedTask = await Task.WhenAny(tasklist);
if (finishedTask.Status == TaskStatus.RanToCompletion)
return await finishedTask;
tasklist.Remove(finishedTask);
}
return "Error: No task finished";
}
1) Creates a list of await ReadLineAsync()
2) While the size of that list is greater than 0, we wait for await any of the ReadLineAsync functions.
3) When we hit a Task that has finished, we return it's string, and exit the function
4) Any remaining ReadLineAsync functions that did not finish are still running, but we lost the reference to their instance.
5) This function loops, calling AllReadAsync() immediately after it finishes.
6) This causes us to try and access the StreamReady while it is still being awaited from step 4 - Thus throwing an excpetion.
Handling Multiple TcpClients with an Async TCP Server
Because of the structure of this, I could not come up with a way to use WhenAny() in my application. Instead I added this function to my Client Class:
public async Task<string> CheckForDataAsync()
{
if (!stream.DataAvailable)
{
await Task.Delay(5);
return "";
}
return await reader.ReadLineAsync();
}
Instead of awaiting ReadLineAsync(), we instead access the NetworkStream from the TcpClient, and we check if there is data available, if(!stream.DataAvailable), if there is not, we return early with an empty string, else we await ReadLineAsync() because we know we have incoming data, and we expect to receive the whole line.
We then replace the first function I talked about, AllReadLineAsync() With the following:
public async Task<string> AllReadLineAsync()
{
string data = "", packet = "";
foreach (var client in clients)
{
data = await client.CheckForDataAsync();
if (data != string.Empty)
packet += string.Format($"[client] {data}\n");
}
return packet;
}
Which is even simpler than the previous way I was trying. This now iterates through all of our clients in a for loop, and calls the CheckForDataAsync() function on each client. Since this functions returns early instead of infinitely awaiting a full ReadLineAsync() it does not continue to run in the background after the AllReadLineAysnc() function ends.
After we finish looping through all of our clients, we take our string packet and return it to the UI context, where we can then add our data to a text box as such:
private async void RecvData(object sender, RoutedEventArgs e)
{
while(server.hasClient)
{
string text = await server.AllReadLineAsync();
txtOutputLog.AppendText(text);
}
}
And that's it. That's how I'm handling multiple TcpClients from within a WPF application.

Synchronous call not working but asynchronous does

I have a simple method that works in an asynchronous format, but when I try to change it to a synchronous format, it acts like it deadlocks and never returns anything. I know it is more effective to use asynchronous calls but I am curious as to why this is happening. Thanks in advance.
This returns a working result:
public static async Task getCompanyAsync(){
var client = new getRestClient();
var response = await companiesApi.GetCompaniesAsync(client);
var companies = response.GetResultAsync<List<Company>>();
}
This will make the program sit and spin until I kill it:
public static void getCompany(){
var client = new getRestClient();
var response = companiesApi.GetCompanies(client);
var companies = response.GetResult<List<Company>>();
}
Edit:
So here is some more detailed code.
static void Main(string[] args)
{
MainAsync(args).Wait();
Console.ReadLine();
}
public static async Task MainAsync(string[] args)
{
await getCompanyAsync();
getCompany();
}
private static async Task getCompanyAsync()
{
var client = getApiClient();
var companiesApi = new CompaniesApi(client);
var response = await companiesApi.GetCompaniesAsync();
var companyies = await response.GetResultAsync<List<Company>>();
foreach (var company in companyies)
{
Console.WriteLine(company.Name);
}
}
private static void getCompany()
{
var client = getApiClient();
var companiesApi = new CompaniesApi(client);
var response = companiesApi.GetCompanies();
var companyies = response.GetResult<List<Company>>();
foreach (var company in companyies)
{
Console.WriteLine(company.Name);
}
I the application ran as expected when I moved getCompany(); from the MainAsync() method into the Main() method.
Two points here.
First, async will often work in unexpected ways in a console application (as compared to a UI application). This is because UIs have a "built in" synchronization contexts (and, obviously, because they don't automatically exit after their "main" method equivalent has completed execution), whereas Console applications don't. See this article for directions on how to use a context in a console application to mitigate this.
Secondly, as pointed out in one of Stephen Cleary's excellent articles on the topic, it's a bad practice to block on async if avoidable as it can lead to exactly the kinds of deadlocks you describe. Basically, one way of solving this is to use async "all the way down" - i.e. use async/await instead of GetResult() in your second sample. This idea is echoed in the Microsoft documentation of best practices for async, which recommend against mixing synchronous and asynchronous code like you do in the second code sample.
In the article I linked to the author explains how a deadlock can occur when you mix synchronous and asynchronous code. Basically, this can occur when, for example, the async method is waiting for the same context as the GetResults() call is. (His article explains it a lot better than me though :) ).

Categories