C# 异步死锁
C# Async deadlock
我在使用外部库的异步方法时遇到 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();
}
}
}
我该如何调试这个死锁?
I am reasonably new to async programming so it's possible I have missed something very obvious here.
有一些关于 async
的通用指南:
- Avoid
async void
- Async all the way, a.k.a., Don't block on async code(link 我的博客)
这些是指南,而不是hard-and-fast规则,但一般来说,如果您遵循它们,您就会减轻痛苦。
//This is a constructor so it can't be async.
没错。所以你需要做点别的。不知何故,您将不得不打破准则(即阻塞异步代码),或者重构代码,以便构造函数不需要调用异步代码。在这种情况下(在大多数情况下),我推荐后者。
因为这是一个构造函数,并且由于您正在观察死锁,所以我假设您正在编写 GUI 应用程序。如果这个假设是正确的,那么不推荐阻塞异步代码——虽然可以使用像 thread pool hack 这样的 hack;阻塞 UI 线程会导致糟糕的用户体验。
GUI 应用程序更好的解决方案是立即(同步)将 UI 初始化为“正在加载”状态,然后 启动 异步操作。然后,当操作完成时,更新 UI 进入其正常的“数据显示”状态。代码更复杂,但它确实提供了更好的用户体验(并且还避免了 OS 抱怨您的应用程序挂起)。
如果这是一个 XAML GUI 应用程序,那么我有一个 article 解释了如何设置“通知任务”对象。然后,您的代码在创建该对象时开始操作,并且可以在操作完成时使用数据绑定更新其 UI。类似的模式可用于其他 GUI 应用程序框架。
我在使用外部库的异步方法时遇到 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();
}
}
}
我该如何调试这个死锁?
I am reasonably new to async programming so it's possible I have missed something very obvious here.
有一些关于 async
的通用指南:
- Avoid
async void
- Async all the way, a.k.a., Don't block on async code(link 我的博客)
这些是指南,而不是hard-and-fast规则,但一般来说,如果您遵循它们,您就会减轻痛苦。
//This is a constructor so it can't be async.
没错。所以你需要做点别的。不知何故,您将不得不打破准则(即阻塞异步代码),或者重构代码,以便构造函数不需要调用异步代码。在这种情况下(在大多数情况下),我推荐后者。
因为这是一个构造函数,并且由于您正在观察死锁,所以我假设您正在编写 GUI 应用程序。如果这个假设是正确的,那么不推荐阻塞异步代码——虽然可以使用像 thread pool hack 这样的 hack;阻塞 UI 线程会导致糟糕的用户体验。
GUI 应用程序更好的解决方案是立即(同步)将 UI 初始化为“正在加载”状态,然后 启动 异步操作。然后,当操作完成时,更新 UI 进入其正常的“数据显示”状态。代码更复杂,但它确实提供了更好的用户体验(并且还避免了 OS 抱怨您的应用程序挂起)。
如果这是一个 XAML GUI 应用程序,那么我有一个 article 解释了如何设置“通知任务”对象。然后,您的代码在创建该对象时开始操作,并且可以在操作完成时使用数据绑定更新其 UI。类似的模式可用于其他 GUI 应用程序框架。