Search code examples
c#wpfasynchronousasync-awaitrestsharp

Async method - weird behavior: my app is getting stuck


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);

Solution

  • 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.