异步方法 - 奇怪的行为:我的应用程序卡住了

Async method - weird behavior: my app is getting stuck

我的异步方法有一个奇怪的问题。我在 VS 的解决方案中创建了第二个项目,将我的应用程序连接到 API(为此使用 RestSharp)。我做了依赖等等

问题是当我通过单击按钮从 UI 调用此方法时(它只启动后端代码,与 UI 等无关)应用程序卡住了。没有错误,我在输出 window 中看到的唯一内容是“线程 ****** 已退出,代码为 0 (0x0)”。而且它会无限地发展。

我使用了该代码(仅来自负责连接 api 并从中获取数据的项目)并制作了一个新的解决方案,新项目,但是准确地复制了代码并且工作正常。

这是我在“主”WPF 应用程序中使用 ICommand 等调用的方法:

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

那些是 类 在 API 项目中:

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

        }
    }

应用程序在线卡住:

RestResponse restResponse = await _bl.Client.PostAsync(Request);

切勿在不完整的 Task 上调用 Task.Result 以避免应用程序死锁。始终等待 Task.
C# 不允许 async 构造函数。构造函数旨在 return 在一些简短的初始化之后快速。它们不是 long-running 操作或启动后台线程的地方(即使允许使用异步构造函数)。
有一些解决方案可以避免 async 构造函数的要求。

  1. 使用 Lazy<T>AsyncLazy<T> 的简单替代解决方案(需要通过 NuGet 包管理器安装 Microsoft.VisualStudio.Threading 包)。 Lazy<T> 允许推迟昂贵资源的实例化或分配。
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. 您可以使用方法而不是 属性
  2. 公开数据
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. 使用必须在使用实例之前调用的 InitializeAsync 方法
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. 通过将昂贵的参数传递给构造函数来实例化实例
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. 使用工厂方法和私有构造函数
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();
}

核心问题 - 正如其他人所指出的那样 - 是您的代码阻塞了异步代码,you shouldn't do(正如我在我的博客上解释的那样)。对于 UI 应用尤其如此,当 UI 线程被阻塞时,这些应用会带来糟糕的用户体验。因此,即使代码没有死锁,阻塞异步代码也不是一个好主意。

如果您想要良好的用户体验,UI 应用程序中的某些地方代码 不能 阻止。 View 和 ViewModel 构造是其中两个地方。创建 VM 时,OS 要求您的应用立即 UI 显示它,在显示数据之前等待网络请求只是一个糟糕的体验。

相反,您的应用程序应该立即return UI (同步) 初始化并显示它。如果非要做网络请求才能显示一些数据,一般是同步初始化UI进入“loading”状态,开始网络请求,然后到那时 construction/initialization 就完成了。稍后,当网络请求完成时,UI 更新为“已加载”状态。

如果你想采用这种方法,我的 Nito.Mvvm.Async package which may help. Its design is described in this article 中有一个 NotifyTask<T> 类型,用法看起来像这样(假设 OrderService 实际上是一个 ViewModel):

public class OrderService
{
  private BaseAPIRepository _repo;

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

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

然后,您可以从 data-binding 到 OrderService.OrdersList,而不是 data-bind 到 OrderService.OrdersList.ResultOrderService.OrdersList.IsCompleted