异步方法 - 奇怪的行为:我的应用程序卡住了
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
构造函数的要求。
- 使用
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);
}
- 您可以使用方法而不是 属性
公开数据
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();
}
- 使用必须在使用实例之前调用的
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();
}
- 通过将昂贵的参数传递给构造函数来实例化实例
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 };
}
- 使用工厂方法和私有构造函数
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.Result
、OrderService.OrdersList.IsCompleted
等
我的异步方法有一个奇怪的问题。我在 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
构造函数的要求。
- 使用
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);
}
- 您可以使用方法而不是 属性 公开数据
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();
}
- 使用必须在使用实例之前调用的
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();
}
- 通过将昂贵的参数传递给构造函数来实例化实例
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 };
}
- 使用工厂方法和私有构造函数
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.Result
、OrderService.OrdersList.IsCompleted
等