我在使用外部库中的异步方法的 C# 中遇到死锁问题。我对异步编程相当陌生,所以我可能在这里错过了一些非常明显的东西。我一直在使用的库是 nethereum,下面的代码将挂起而不会抛出任何错误,而且我不知道如何调试它。
public Account(Wallet wallet, string name = "") //This is a constructor so it can't be async.
{
Console.WriteLine("########################################################## Starting Account Constructor");
Task.Run(async () => await GetTokens()); //If I change this to a .wait() it will hang one call earlier at TokenListService
Console.WriteLine("########################################################## Finishing Account Constructor");
}
public async Task GetTokens()
{
Console.WriteLine("########################################################## Starting Account GetTokens");
Web3 web3 = new Web3(Globals.web3Endpoint);
Console.WriteLine("########################################################## Starting Account LoadFromUrl");
var tokens = await new TokenListService().LoadFromUrl(TokenListSources.UNISWAP);
Console.WriteLine("########################################################## Starting Account GetAllTokenBalancesUsingMultiCallAsync");
//Hangs here
var tokensOwned = await web3.Eth.ERC20.GetAllTokenBalancesUsingMultiCallAsync(
new string[] { privateKey }, tokens.Where(x => x.ChainId == 1),
BlockParameter.CreateLatest());
//This never runs
Console.WriteLine("########################################################## Starting Account GetTotalBalance");
}
除上述之外,当我在独立的控制台应用程序中运行代码时,该代码也可以工作。下面的示例应该运行良好。
namespace testProj
{
class Program
{
public static async Task GetERC20Balances()
{
var web3 = new Web3(“<endpoint>”);
var tokens = await new TokenListService().LoadFromUrl(TokenListSources.UNISWAP);
var owner = “<account address>”;
var tokensOwned = await web3.Eth.ERC20.GetAllTokenBalancesUsingMultiCallAsync(
new string[] { owner }, tokens.Where(x => x.ChainId == 1),
BlockParameter.CreateLatest());
Console.WriteLine(“This works…”);
}
static void Main()
{
GetERC20Balances().Wait();
Console.WriteLine("Finished!");
Console.ReadLine();
}
}
}
我该如何调试这个死锁?
我对异步编程相当陌生,所以我可能在这里错过了一些非常明显的东西。
有一些与
async
相关的共同准则:
async void
这些是指南,不是硬性规定,但一般来说,如果您遵循它们,您会减少痛苦。
//这是一个构造函数,所以它不能是异步的。
是的。所以你需要做点别的事情。无论如何,您必须打破准则(即阻止异步代码),或者重新构造代码,以便构造函数不需要调用异步代码。在这种情况下(并且在大多数情况下),我推荐后者。
因为这是一个构造函数,并且由于您正在观察死锁,所以我假设您正在编写一个 GUI 应用程序。如果这个假设是正确的,那么不建议阻塞异步代码 - 虽然可以使用像线程池 hack 这样的 hack ;阻塞 UI 线程会导致用户体验不佳。
GUI 应用程序更好的解决方案是立即(同步)将 UI 初始化为“正在加载”状态,并启动异步操作。然后,当操作完成时,更新 UI 进入正常的“数据显示”状态。代码更复杂,但它确实提供了更好的用户体验(并且还避免了操作系统抱怨您的应用程序挂起)。
如果这是一个 XAML GUI 应用程序,那么我有一篇 article 解释如何设置“通知任务”对象。然后,您的代码在创建该对象时启动该操作,并可以在操作完成时使用数据绑定来更新其 UI。类似的模式可用于其他 GUI 应用程序框架。
添加特定于 UI MVVM 编码的答案。
这是为了响应.NET MAUI 应用程序在导航到具有数据绑定的页面时崩溃。
考虑到 MVVM 的这种尝试:
// Code-behind of a Maui page:
public partial class SomePage : ContentPage
{
public SomePage()
{
InitializeComponent();
BindingContext =new SomePageViewModel();
}
}
// ViewModel:
public partial class SomePageViewModel : ObservableObject
{
public SomePageViewModel()
{
// CAUSES DEADLOCK: attempt to call async method from constructor on UI thread.
Initialize();
}
private async void Initialize()
{
...
var client = new HttpClient();
... = await client.GetAsync(url); // SLOW: web server Request/Response.
...
}
}
死锁
async
的情况下调用 await
方法。 CreateProductPageViewModel()
调用这样的方法。await
放入构造函数中,因此请重构代码。将对异步方法的调用移出构造函数。// Code-behind of a Maui page:
public partial class SomePage : ContentPage
{
public SomePage()
{
InitializeComponent();
Task.Run( async () =>
{ // This code runs AFTER constructor returns.
// The page will appear empty, until "Create" returns, and BindingContext is set.
// [Beyond scope of answer: show a busy indicator while data is fetched.]
var vm = new SomePageViewModel();
await SomePageViewModel.Create();
BindingContext = vm;
});
}
}
// ViewModel:
public partial class SomePageViewModel : ObservableObject
{
public static async SomePageViewModel Create()
{
var it = new SomePageViewModel();
await it.Initialize(); // "Init" or "Initialize" would be good name here.
return it;
}
public SomePageViewModel()
{
}
private async void Initialize()
{
...
var client = new HttpClient();
... = await client.GetAsync(url); // SLOW: web server Request/Response.
...
}
}
另请参阅:
c# static async factory method