0

I have a weird problem with my async method. I made second project in my solution in VS to connect my app to an API (using RestSharp for it). I made dependencies etc.

The problem is when I call this method from the UI by clicking a button (it only start backend code, there is no relation to UI etc.) app getting stuck. There is no errors, the only things I can see in the output window are "The thread ****** has exited with code 0 (0x0)." and it's going infinitely.

I took that code (only from project responsible for connecting and taking data from an api) and made a new solution, new project, but exacly copied code and it is working fine.

This is method what I am calling in the "main" WPF app using ICommand etc:

private void Api()
{
    _orderService = new OrderService();
}

And those are classes in API project:

BLContext.cs

public class BLContext
{
    private RestClient _client;
    public RestClient Client { get; set; }

    private string _token;
    public string Token { get; set; }

    public BLContext()
    {
        Client = new RestClient("https://api.baselinker.com/connector.php");
        Token = "************************";
    }
}

BaseAPIRepository.cs

public class BaseAPIRepository
    {
        private BLContext _bl = new BLContext();
        RestRequest Request = new RestRequest();

        public BaseAPIRepository() { }


        public async Task<List<Order>> GetOrders()
        {
            List<Order> orders = new List<Order>();
            List<JToken> orderList = new List<JToken>();

            StartRequest("getOrders");
            Request.AddParameter("parameters", "{ \"status_id\": 13595 }");
            Request.AddParameter("parameters", "{ \"get_unconfirmed_orders\": false }");

            RestResponse restResponse = await _bl.Client.PostAsync(Request);
            JObject response = (JObject)JsonConvert.DeserializeObject(restResponse.Content);

            orderList = response["orders"].ToList();

            foreach (JToken order in orderList)
            {
                Order newOrder = new Order();
                newOrder.Id = (int)order["order_id"];
                newOrder.ProductsInOrder = GetProductsFromOrder((JArray)order["products"]);

                orders.Add(newOrder);
            }

            return orders;

        }

        public void StartRequest(string method)
        {
            Request.AddParameter("token", _bl.Token);
            Request.AddParameter("method", method);
        }

        public List<OrderedProduct> GetProductsFromOrder(JArray productsInOrder)
        {
            List<OrderedProduct> tmpListOfProducts = new List<OrderedProduct>();
            foreach (var item in productsInOrder)
            {
                OrderedProduct tmpOrderedProduct = new OrderedProduct();
                //tmpOrderedProduct.Id = (int)item["product_id"];
                tmpOrderedProduct.Signature = (string)item["sku"];
                tmpOrderedProduct.Quantity = (int)item["quantity"];
                tmpListOfProducts.Add(tmpOrderedProduct);
            }

            return tmpListOfProducts;
        }
    }

OrderService.cs

public class OrderService
    {
        private BaseAPIRepository _repo;

        private List<Order> _ordersList;
        public List<Order> OrdersList { get; set; }


        public OrderService()
        {
            _repo = new BaseAPIRepository();
            OrdersList = new List<Order>();

            OrdersList = _repo.GetOrders().Result;
            Console.WriteLine("Test line to see if it passed 24th line.");

        }
    }

App is getting stuck on line:

RestResponse restResponse = await _bl.Client.PostAsync(Request);
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • what do you see when you break in with VS debugger? – pm100 Apr 10 '22 at 16:57
  • And what kind of application is this? – Jon Skeet Apr 10 '22 at 16:57
  • 2
    It's also confusing to have `_client`, `_token` and `_orderList` fields when you also have `Client`, `Token` and `OrdersList` auto-implemented properties... – Jon Skeet Apr 10 '22 at 16:58
  • @JonSkeet WPF, kinda hidden in the question body – DavidG Apr 10 '22 at 17:01
  • 4
    It really doesn't help that you are calling `.Result` inside the `OrderService` constructor. That's probably where you're getting a deadlock or error of some sort. – DavidG Apr 10 '22 at 17:02
  • @JonSkeet Warehouse managment system in WPF MVVM. Do not worry about those proporties they are in this form for future implementation. – Świętopeł Ziemowit Apr 10 '22 at 17:06
  • @pm100 Everything looks good as it supposed to. I even made a comparision between working app (copied code to another project) and this (not working project). Everything is the same, the difference is that in this not working project program get stuck on this line. – Świętopeł Ziemowit Apr 10 '22 at 17:07
  • 2
    dont do that 'lets convert from async to sync' thing of calling .Result. It will deadlock. Async all the way – pm100 Apr 10 '22 at 17:09
  • 2
    Task.Result may cause a deadlock. Add an `async` Initialize method to your OrderService that calls `await _repo.GetOrders()` and await that method when you call it. – Clemens Apr 10 '22 at 17:09
  • 3
    "Do not worry about those proporties they are in this form for future implementation" - that's not very helpful when asking a question. Ideally, you'd provide a [mcve], and certainly not include anything confusing that's completely irrelevant to the problem. – Jon Skeet Apr 10 '22 at 17:11
  • 1
    Important reading: [Don't Block on Async Code](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) by Stephen Cleary. For solutions to your problem, look here: [Can constructors be async?](https://stackoverflow.com/questions/8145479/can-constructors-be-async) – Theodor Zoulias Apr 10 '22 at 18:29

2 Answers2

2

The core problem - as others have noted - is that your code is blocking on asynchronous code, which you shouldn't do (as I explain on my blog). This is particularly true for UI apps, which deliver a bad user experience when the UI thread is blocked. So, even if the code wasn't deadlocking, it wouldn't be a good idea to block on the asynchronous code anyway.

There are certain places in a UI app where the code simply cannot block if you want a good user experience. View and ViewModel construction are two of those places. When a VM is being created, the OS is asking your app to display its UI right now, and waiting for a network request before displaying data is just a bad experience.

Instead, your application should initialize and return its UI immediately (synchronously), and display that. If you have to do a network request to get some data to display, it's normal to synchronously initialize the UI into a "loading" state, start the network request, and then at that point the construction/initialization is done. Later, when the network request completes, the UI is updated into a "loaded" state.

If you want to take this approach, there's a NotifyTask<T> type in my Nito.Mvvm.Async package which may help. Its design is described in this article and usage looks something like this (assuming OrderService is actually a ViewModel):

public class OrderService
{
  private BaseAPIRepository _repo;

  public NotifyTask<List<Order>> OrdersList { get; set; }

  public OrderService()
  {
    _repo = new BaseAPIRepository();
    OrdersList = NotifyTask.Create(() => _repo.GetOrders());
  }
}

Then, instead of data-binding to OrderService.OrdersList, you can data-bind to OrderService.OrdersList.Result, OrderService.OrdersList.IsCompleted, etc.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you so much! I am learning programming by my self. Some times it's hard to find an answer when you do not really know how to name your problem for example "DeadLock". – Świętopeł Ziemowit Apr 13 '22 at 12:11
1

You should never call Task.Result on an incomplete Task to avoid deadlocking the application. Always await a Task.
C# doesn't allow async constructors. Constructors are meant to return fast after some brief initialization. They are not a place for long-running operations or starting background threads (even if async constructors were allowed).
There are a few solutions to avoid the requirement of async constructors.

  1. A simple alternative solution using Lazy<T> or AsyncLazy<T> (requires to install the Microsoft.VisualStudio.Threading package via the NuGet Package Manager). Lazy<T> allows to defer the instantiation or allocation of expensive resources.
public class OrderService
{
  public List<object> Orders => this.OrdersInitializer.GetValue();
  private AsyncLazy<List<object>> OrdersInitializer { get; }

  public OrderService()
    => this.OrdersInitializer = new AsyncLazy<List<object>>(InitializeOrdersAsync, new JoinableTaskFactory(new JoinableTaskContext()));

  private async Task<List<object>> InitializeOrdersAsync()
  {
    await Task.Delay(TimeSpan.FromSeconds(5));
    return new List<object> { 1, 2, 3 };
  }
}

public static void Main()
{
  var orderService = new OrderService();

  // Trigger async initialization
  orderService.Orders.Add(4);
}
  1. You can expose the data using a method instead of a property
public class OrderService
{
  private List<object> Orders { get; set; }

  public async Task<List<object>> GetOrdersAsync()
  {
    if (this.Orders == null)
    {
      await Task.Delay(TimeSpan.FromSeconds(5));
      this.Orders = new List<object> { 1, 2, 3 };
    }
    return this.Orders;
  }
}

public static async Task Main()
{
  var orderService = new OrderService();

  // Trigger async initialization
  List<object> orders = await orderService.GetOrdersAsync();
}
  1. Use an InitializeAsync method that must be called before using the instance
public class OrderService
{
  private List<object> orders;
  public List<object> Orders 
  { 
    get
    {
      if (!this.IsInitialized)
      {
        throw new InvalidOperationException(); 
      }
      return this.orders;
    }
    private set
    {
      this.orders = value;
    }
  }

  public bool IsInitialized { get; private set; }

  public async Task<List<object>> InitializeAsync()
  {
    if (this.IsInitialized)
    {
      return;
    }

    await Task.Delay(TimeSpan.FromSeconds(5));
    this.Orders = new List<object> { 1, 2, 3 };
    this.IsInitialized = true;
  }
}

public static async Task Main()
{
  var orderService = new OrderService();

  // Trigger async initialization
  await orderService.InitializeAsync();
}
  1. Instantiate the instance by passing the expensive arguments to the constructor
public class OrderService
{
  public List<object> Orders { get; }

  public async Task<List<object>> OrderService(List<object> orders)
    => this.Orders = orders;
}

public static async Task Main()
{
  List<object> orders = await GetOrdersAsync();

  // Instantiate with the result of the async operation
  var orderService = new OrderService(orders);
}

private static async Task<List<object>> GetOrdersAsync()
{
  await Task.Delay(TimeSpan.FromSeconds(5));
  return new List<object> { 1, 2, 3 };
}
  1. Use a factory method and a private constructor
public class OrderService
{
  public List<object> Orders { get; set; }

  private OrderServiceBase()  
    => this.Orders = new List<object>();

  public static async Task<OrderService> CreateInstanceAsync()
  {
    var instance = new OrderService();
    await Task.Delay(TimeSpan.FromSeconds(5));
    instance.Orders = new List<object> { 1, 2, 3 };
    return instance;
  }
}

public static async Task Main()
{
  // Trigger async initialization  
  OrderService orderService = await OrderService.CreateInstanceAsync();
}
BionicCode
  • 1
  • 4
  • 28
  • 44