In my WPF application (.NET 8.0) I want to consume a asynchronous operation from a 3rd party library. When testing with all code inside the MainWindow.xaml.cs it works smoothely. When I move the same code into a separate class, using a static constructor, the operation is blocking indefinitely even with WaitAsync.
Here is my code:
public class VirtuosoCommunication
{
..
public static VirtuosoCommunication? Create(...)
{
VirtuosoCommunication vc = new VirtuosoCommunication();
// setting private variables for vc
if (!vc.TestRead(log)) return null;
if (!vc.TestWrite(log)) return null;
return vc;
}
private bool TestRead(Log? log)
{
string testGraph = "...";
Task<Graph?> task = LoadGraphFromSparqlRemoteEndpoint(testGraph, log);
Graph? g = task.Result;
return g != null;
}
internal async Task<Graph?> LoadGraphFromSparqlRemoteEndpoint(string graphFullName, Log? log)
{
Uri baseUri = new Uri(_readEndpoint ?? "");
HttpClient client = new HttpClient();
client.BaseAddress = baseUri;
SparqlQueryClient sparqlQueryClient = new SparqlQueryClient(client, baseUri);
Graph result = new Graph();
IGraph? tmp = null;
try
{
while (tmp == null || tmp.Triples.Count == limit)
{
string query = "construct { ?s ?p ?o } FROM <" + graphFullName + "> where {?s ?p ?o.} OFFSET " + offSet.ToString() + " LIMIT " + limit.ToString();
Task<IGraph> task = sparqlQueryClient.QueryWithResultGraphAsync(query);
tmp = await task.WaitAsync(TimeSpan.FromSeconds(4));
...
}
}
catch (Exception ex) { ... }
return result;
}
When I run the code line by line, it stops executing at tmp = await task.WaitAsync. When I then hit the "Break execution" Button in Visual Studio, it is showing me the line Graph? g = task.Result
in the TestRead method to be the next to be executed when the thread returns.
As I said, the same async call in a test routine completely placed in MainWindow.xaml.cs works without problem, so I guess I am somehow using the async call wrongly.
UPDATE 25.11.2024
From the answer of this question I changed the TestRead
method to:
private bool TestRead(Log? log)
{
string testGraph = "...";
//Task<Graph?> task = LoadGraphFromSparqlRemoteEndpoint(testGraph, log);
Task<Graph?> task = Task.Run<Graph?>(async() => await LoadGraphFromSparqlRemoteEndpoint(testGraph, log));
Graph? g = task.Result;
return g != null;
}
It works this way (yeah!), but I am not sure if this is the correct way to do it. Regarding parallelization, I just need the call to the SPARQL endpoint to not block my UI, I don’t need to run other, parallel tasks meanwhile.
2
Answers
As Fildor said in the comments, you’re not using a static
cctor
; you’re using a static factory method.And that’s a good thing. A
ctor
orcctor
cannot beasync
, so you wouldn’t be able toawait
anything. But a factory method can beasync
. And in this case, it should be:Next, I’d suggest looking at your
HttpClient
usage, which is problematic:You should consider using
IHttpClientFactory
instead of creating a newHttpClient
instance on every call.Use IHttpClientFactory to implement resilient HTTP requests – .NET | Microsoft Learn
Your updated answer using
Task.Run()
is likely correct, but it is not completely clear without context:When you’re awaiting tasks with
await DoSomething();
the current synchronization context is captured (if called from a button click in WPF, this will be the main thread). When the async operation completes, the state machine will schedule the next line of code to be executed in the same context (ie. the main thread). However, since your callingtask.Result
inTestRead
you’re already blocking the main thread, resulting in a deadlock.Calling
Task.Run()
avoids this problem by making sure everything "inside" the Task.Run block is executed on the thread pool, which does not preserve synchronization context and will not deadlock as it will just spawn new threads if all of them are blocked.Please note that it would still be better to use async / await all the way through and avoid
Task.Result
andTask.Wait()
as much as possible. If you can change theTestRead
function to return a Task instead (like Richard Deeming proposed here https://stackoverflow.com/a/79214898/1074014) definitely go for this approach!