Search code examples
c#asynchronousasync-await

async await blocks execution even with task.WaitAsync


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.


Solution

  • 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 or cctor cannot be async, so you wouldn't be able to await anything. But a factory method can be async. And in this case, it should be:

    public static async Task<VirtuosoCommunication?> Create(...)
    {
        VirtuosoCommunication vc = new VirtuosoCommunication();
        // setting private variables for vc
    
        if (!await vc.TestRead(log)) return null;
        if (!await vc.TestWrite(log)) return null;
    
        return vc;
    }
    
    private async Task<bool> TestRead(Log? log)
    {
        string testGraph = "...";
        Graph? g = await LoadGraphFromSparqlRemoteEndpoint(testGraph, log);
        return g != null;
    }
    

    Next, I'd suggest looking at your HttpClient usage, which is problematic:

    You should consider using IHttpClientFactory instead of creating a new HttpClient instance on every call.

    Use IHttpClientFactory to implement resilient HTTP requests - .NET | Microsoft Learn