I am having trouble with a deadlock in C# using an async method from an external library. I am reasonably new to async programming so it's possible I have missed something very obvious here. The library I have been using is nethereum and the following code will hang without throwing any errors and I don't know how to debug it.
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");
}
Additionally to the above the code works when I run it in a stand alone console application. The below example should run fine.
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();
}
}
}
How would I go about debugging this deadlock?
CodePudding user response:
I am reasonably new to async programming so it's possible I have missed something very obvious here.
There's a couple common guidelines with async
:
- Avoid
async void
- Async all the way, a.k.a., Don't block on async code (link to my blog)
These are guidelines, not hard-and-fast rules, but in general if you follow them you'll have less pain.
//This is a constructor so it can't be async.
That's correct. So you'll need to do something else. Somehow or another you'll have to either break the guideline (i.e., block on the asynchronous code), or restructure the code so that the constructor does not need to call asynchronous code. In this case (and in most cases), I recommend the latter.
Since this is a constructor, and since you're observing a deadlock, I'm assuming that you are writing a GUI application. If that assumption is correct, then blocking on asynchronous code - while possible using a hack like the thread pool hack - is not recommended; blocking the UI thread results in a poor user experience.
A better solution for GUI applications is to immediately (and synchronously) initialize the UI into a "loading" state, and start the asynchronous operation. Then, when the operation completes, update the UI into its normal "data display" state. The code is more complex, but it does provide a better user experience (and also avoids the OS complaining that your app is hung).
If this is a XAML GUI app, then I have an article that explains how to set up a "notifying task" object. Your code then starts the operation when it creates that object, and can use data binding to update its UI when the operation completes. Similar patterns can be used for other GUI application frameworks.