gpt4 book ai didi

C# Async deadlock(C#异步死锁)

转载 作者:bug小助手 更新时间:2023-10-24 22:43:21 28 4
gpt4 key购买 nike



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.

我在使用外部库中的异步方法时遇到了C#中的死锁问题。我对异步编程相当陌生,所以我可能在这里错过了一些非常明显的东西。我一直在使用的库是空虚的,以下代码将挂起而不会抛出任何错误,我不知道如何调试它。


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?

我该如何着手调试这个死锁呢?


更多回答

" to the above the code works when I run it in a stand alone console application" and where does it fail (is it desktop app?). And in general - do not run async code in sync mode (i.e. Wait, Result and so on), so do not use async code in constructors.

“如上所述,当我在独立的控制台应用程序中运行代码时,它可以工作”,以及它在哪里失败(它是桌面应用程序吗?)。一般来说,不要在同步模式下运行异步代码(即等待、结果等),因此不要在构造函数中使用异步代码。

Maybe move the async call into an async Init method that you call after you create your class? Also you can do an static async Task Main() instead.

也许可以将异步调用移到创建类后调用的异步Init方法中?此外,您还可以执行静态异步任务main()。

As for the issue - it seems that you have a pretty standard (but still cumbersome) case of blocking on async code in presence of synchronization context. As linked article suggests - do not do it.

至于这个问题--在存在同步上下文的情况下,您似乎有一个非常标准的(但仍然很麻烦的)阻塞异步代码的情况。正如链接文章所建议的那样--不要这样做。

Move the call to GetTokens() outside the Constructor and use await [Account Instance].GetTokens() after you have initialized the Account class.

将对GetTokens()的调用移到构造函数之外,并在初始化Account类之后使用aWait[Account实例].GetTokens()。

优秀答案推荐


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:

关于异步,有几条常见的指导原则:



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.

因为这是一个构造函数,而且您正在观察死锁,所以我假定您正在编写一个GUI应用程序。如果这一假设是正确的,则不推荐在异步代码上阻塞-尽管可以使用线程池黑客之类的黑客;阻塞UI线程会导致糟糕的用户体验。


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

对于图形用户界面应用程序,更好的解决方案是立即(并且同步地)将UI初始化为“正在加载”状态,并启动异步操作。然后,当操作完成时,将UI更新到其正常的“数据显示”状态。代码更复杂,但它确实提供了更好的用户体验(也避免了操作系统抱怨你的应用程序挂起)。


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.

如果这是一个XAML图形用户界面应用程序,那么我有一篇文章解释了如何设置“通知任务”对象。然后,代码在创建该对象时启动该操作,并可以在操作完成时使用数据绑定来更新其UI。类似的模式也可以用于其他图形用户界面应用程序框架。



This answer is specific to UI MVVM coding. To show a way to apply the accepted answer.

这个答案特定于UIMVVM编码。来展示一种应用公认答案的方法。


This is in response to .NET MAUI App Crashes on Navigating to a Page with Data Binding.

这是对.NET Maui App在导航到具有数据绑定的页面时崩溃的响应。


This attempt at MVVM causes a deadlock (app UI freezes):

在MVVM上的此尝试会导致死锁(应用程序UI冻结):


// 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.
...
}
}



TO AVOID DEADLOCK

避免僵局



  • DO NOT call async methods without await. SomePageViewModel() calls such a method.

  • You can't put await except inside an async context, so refactor the code. Best is to move calls to async method(s) out of the constructor.

  • One solution is a "static async factory method":


// Code-behind of a Maui page:
public partial class SomePage : ContentPage
{
public SomePage()
{
InitializeComponent();
// DO NOT set BindingContext here. This is done later.
}

private bool isInitialized;

// This is method used by Maui. Other UI frameworks may have a different name.
protected override async void OnAppearing()
{
base.OnAppearing();

// OnAppearing is also called when we go back to page later.
// This flag avoids initializing again.
if (!isInitialized)
{
isInitialized = true;
await SetBindingContext();
}
}

private async Task SetBindingContext()
{
// The page may appear empty, until BindingContext is set.
// [Beyond scope of answer: show a busy indicator while data is fetched.]
var vm = await SomePageViewModel.Create();
// ...set vm properties here as needed...
BindingContext = vm;
}
}

// ViewModel:
public partial class SomePageViewModel : ObservableObject
{
// "static async factory method".
// We do this instead of the "Task.Run" solution shown above,
// because caller wants the object AFTER Initialize has run.
public static async SomePageViewModel Create()
{
var it = new SomePageViewModel();
await it.Initialize();
return it;
}

public SomePageViewModel()
{
}

private async void Initialize()
{
...
var client = new HttpClient();
... = await client.GetAsync(url); // SLOW: web server Request/Response.
...
}
}



See also:

另见:



更多回答

I don't have 15 reputation so I can't upvote you but your article helped me resolve this so thank you.

我没有15个名声,所以我不能给你加分,但你的文章帮助我解决了这个问题,所以谢谢你。

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com