【问题标题】:Calling a web service via an Async Task from UI thread is causing a deadlock从 UI 线程通过异步任务调用 Web 服务导致死锁
【发布时间】:2015-09-22 03:26:46
【问题描述】:

我是 Xamarin Forms 的新手,并且有一个异步获取事务的应用程序,但是当我调用 Web 服务时它遇到了死锁。

我有一个 TransactionView:

这是 XAML:

<Label Text="Account:" Grid.Row="0" Grid.Column="0" Style="{StaticResource LabelStyle}"/> 
<ctrls:BindablePicker x:Name="ddlAccountsWinPhone" ItemsSource="{Binding Accounts}" SelectedIndex="{Binding AccountID}"  Grid.Row="0" Grid.Column="1" />
<Label Text="From Date:" Grid.Row="2" Grid.Column="0" Style="{StaticResource LabelStyle}"/> <DatePicker x:Name="dtFromWinPhone" Date="{Binding FromDate}" Grid.Row="2" Grid.Column="1"  /> 
<Label Text="To Date:" Grid.Row="3" Grid.Column="0" Style="{StaticResource LabelStyle}"/> <DatePicker x:Name="dtToWinPhone" Date="{Binding ToDate}"  Grid.Row="3" Grid.Column="1" /> 
<Button x:Name="btnViewWinPhone" Text="View" Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"  
Command ="{Binding ShowTransactions}" />

这是 TransactionView 背后的代码,注意我将在名为 TransactionSubPageView 的子页面上显示一个交易网格:

public partial class TransactionView : ContentPage
{
    private TransactionViewModel transactionViewModel;
    private TransactionViewModel ViewModel
    {
        get {
            if (transactionViewModel == null) transactionViewModel = new TransactionViewModel(
             this.Navigation, new TransactionSubPageView()); //Pass sub page to ViewModel ctor

            return transactionViewModel;
        }
    }

    public TransactionView()
    {
        InitializeComponent();
        BindingContext = ViewModel;
    }

这是 TransactionViewModel:

public class TransactionViewModel : ViewModelBase
{
    private int accountID;
    private List<Account> accounts = new List<Account>();
    private Account accountItemSelected;
    private DateTime fromDate = new DateTime(1900,1,1);
    private DateTime toDate = new DateTime(1900, 1, 1);

    private ContentPage transactionGridViewNav;

    public TransactionViewModel(INavigation navigationInterface, ContentPage transactionSubPageView)
    {
        base.navigation = navigationInterface;
        transactionSubPageViewNav = transactionSubPageView; //I SAVE A REFERENCE TO THE SUBPAGE HERE
        accounts.AddRange(Client.Accounts);
    }

    public ICommand ShowTransactions
    {
        get
        {
            return new Command(async () =>
            { 
                //HERE IS WHERE "I THINK" I WANT TO FETCH THE TRANSACTIONS

                //THEN NAVIGATE TO THE THE SUBPAGE WITH THE GRID OF TRANSACTIONS
                await navigation.PushAsync(transactionSubPageViewNav);
            });
        }
    }

    public int AccountID
    {
        get{ return this.accountID; }
        set{
            this.accountID = value;
            this.OnPropertyChanged("AccountID");
            }
    }

    public List<Account> Accounts
    {
        get{
            return this.accounts;}
        set{
            this.accounts = value;
            this.OnPropertyChanged("Accounts");
            }
    }

    public Account AccountItemSelected
    {
        get{return accountItemSelected;}
        set {
                if (accountItemSelected != value)
                {
                    accountItemSelected = value;
                    OnPropertyChanged("AccountItemSelected");
                }
            }
    }

    public DateTime FromDate { get; set; }
    public DateTime ToDate { get; set; }}
    ...

这是 TransactionSubPageView:

public partial class TransactionSubPageView : ContentPage
{
    public TransactionSubPageViewModel transactionSubPageViewModel;

    public TransactionSubPageViewModel ViewModel
    {
        get
        {
            if (transactionSubPageViewModel == null)
                transactionSubPageViewModel = new TransactionSubPageViewModel();
            return transactionGridViewModel;
        }
    }

    private Grid gridTransactions;

    public TransactionSubPageView()
    {
        InitializeComponent();
        BindingContext = ViewModel;
    }

    protected async override void OnAppearing()
    {
        base.OnAppearing();
        //THIS IS A VOID TO POPULATE A GRID AND SET THE PAGE'S CONTENT, IT USES 
        //transactionSubPageViewModel.TransactionsGrid!!
        PopulateGridTransactions();
    }

这是子页面视图模型:

public class TransactionSubPageViewModel : ViewModelBase
{
    public List<Transaction> transactionsGrid = new List<Transaction>();

    public int accountId = 1636;
    public DateTime fromDate = new DateTime(2015, 8, 1);
    public DateTime toDate = new DateTime(2015, 9, 1);

    public TransactionGridViewModel() { }

    public List<Transaction> TransactionsGrid
    {
        get {
            if (transactionsGrid == null) transactionsGrid = MyWebService.GetTransactions(1636, new DateTime(2015, 8, 1), new DateTime(2015, 9, 1)).Result;
            return transactionsGrid;}
    }
}

最后是导致问题的 WebService 调用:

public static async Task<List<Transaction>> GetTransactions(int accountId, DateTime fromDate, DateTime toDate)
{
    var client = new System.Net.Http.HttpClient(new NativeMessageHandler());
    client.BaseAddress = new Uri("http://theWebAddress.com/);
    var response = await client.GetAsync("API.svc/Transactions/" + accountId + "/" + fromDate.ToString("yyyy-MM-dd") + "/" + toDate.ToString("yyyy-MM-dd")); //.ConfigureAwait(false);
    var transactionJson = response.Content.ReadAsStringAsync().Result;
    var transactions = JsonConvert.DeserializeObject<List<Transaction>>(transactionJson);
    return transactions;
}

感谢到目前为止的阅读,问题是 webmethod 调用中的这一行总是挂起:

var response = await client.GetAsync("API.svc/Transactions/" + accountId + "/" + fromDate.ToString("yyyy-MM-dd") + "/" + toDate.ToString("yyyy-MM-dd")); 

如果我从子页面的OnAppearing 事件调用GetTransactions web 服务,它会挂起,如果我从ICommand ShowTransactions 调用它,它也会挂起。我错过了await 还是continue

我有readfair 几个documentssimilar 人和who are confused,我知道我是encountering a deadlock,但我只是不知道如何解决它。

我试过ConfigureAwait(false) 没有运气。如果我可以在页面中的 background thread, show a progress bar and when the operation is complete render the results 上调用 WebService,那就太理想了。

【问题讨论】:

    标签: c# xamarin async-await deadlock xamarin.forms


    【解决方案1】:

    成功!!我知道这将是一个在这里询问的情况,然后我会解决它。有趣的是它是如何工作的!

    这就是我让它工作的方式,请随意批评它:


    我把TransactionSubPageViewModel's TransactionGrid属性变成了一个Async方法,直接把webservice调用放在那里:

    public async Task<List<Transaction>> TransactionsGrid()
    {
        if (transactionsGrid == null)
        {
            var client = new System.Net.Http.HttpClient(new ModernHttpClient.NativeMessageHandler());
            client.BaseAddress = new Uri("http://theWebAddress.com/);
            var response = await client.GetAsync("API.svc/Transactions/" + accountId + "/" + fromDate.ToString("yyyy-MM-dd") + "/" + toDate.ToString("yyyy-MM-dd"));
            var transactionJson = await response.Content.ReadAsStringAsync(); //DONT USE Result HERE, USE AWAIT AS PER @Noseratio suggested
            var transactions = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Transaction>>(transactionJson);
            transactionsGrid = transactions;
        }
            return transactionsGrid;
    }
    

    然后我从我想调用它的地方调用了 Web 服务。

    public ICommand ShowTransactions
    {
        get
        {
            return new Command(async () =>
            { 
                //THIS IS WHERE I WAS MISSING THE AWAIT!!!
                await ((TransactionGridViewModel)transactionSubPageViewNav.BindingContext).TransactionsGrid();
    
                await navigation.PushAsync(transactionSubPageViewNav);
            });
        }
    

    然后,当我从 OnAppearing() 调用 PopulateGridTransactions 时,数据已经可用:

    public void PopulateGridTransactions()
    {
    ...
      foreach (Models.Transaction transaction in transactionSubPageViewModel.TransactionsGrid().Result)
      {
    

    编辑:

    正如@Noseratio 指出的那样,让我解释一下为什么您需要在异步中使用 Try/Catch。

    巧合的是,在收到来自 web 服务的响应后,我在反序列化 json web 服务结果时在下一行出现错误。

    我会在 Xamarin App.cs 中设置一个全局异常处理程序,以便它能够捕获它:

    通过将 Try/Catch 放入异步中,我可以在同一帧上捕获异常(在堆栈被 Application_UnhandledException 捕获之前展开之前 - 这是 very annoying to track down ):

    【讨论】:

    • OT,当您将 async void lambda 传递给 new Command(async () =&gt; { ... }) 时,您应该在其中进行一些异常处理。它们不会被扔到同一个堆栈帧上(因为它们会用于非异步 lambda)。
    • 另外,response.Content.ReadAsStringAsync().Result 可能会给您带来另一个僵局。将其更改为await response.Content.ReadAsStringAsync()。在别处寻找.Result.Wait()
    • 不,将try/catch 放入async() lambda 中。试试这个:try { Action lambda = async () =&gt; { throw new ApplicationException(); } } catch { Debug.Assert(false); }。你永远不会达到断言。更多here.
    • 谢谢你,我真的很感激这两个提示!
    • 干杯 :) 我尽力为下一个需要理解这一点的人解释它。
    猜你喜欢
    • 1970-01-01
    • 2018-02-13
    • 1970-01-01
    • 2019-01-13
    • 1970-01-01
    • 1970-01-01
    • 2014-12-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多