WPF > WEBAPI > HttpClient > AWAIT = UI 延迟



我很困惑,我需要你的帮助。

我有一个使用 ASYNC 和 AWAIT 调用 WEBAPI 的 WPF 应用程序,我的 UI 遇到延迟。

这是我正在使用的代码...

HttpResponseMessage response = await WEBAPI.GetHttpClient().GetAsync("api/COStateType/GetStateTypesByCountryID/" + Constants.COUNTRY_ID_USA);  
response.EnsureSuccessStatusCode(); 
App.stateTypes = await response.Content.ReadAsAsync<List<coStateType>>(); 

GetHttpClient 代码...

 public static HttpClient GetHttpClient()
    {
        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ABS2.WEBAPI.Uri"]);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        return client;
    }

这是主要问题...

在启动期间,我显示一个带有移动齿轮的初始屏幕(动画)。 实现对 WEBAPI 的调用后,动画不再流畅。 我已经读到使用HttpClient的异步调用会导致UI阻塞,并且认为这就是我的动画正在发生的事情。

这是问题...

这个问题有办法吗? 我尝试将此调用包装在后台工作者和/或任务中,但都没有解决问题。 有没有另一种与 WEBAPI 通信的方法不会导致此 UI 阻塞? 我做错了什么?

非常感谢您的帮助!!

@Stef7 & @Panagiotis - 你们说得对,UI 线程被阻止了。 我不确定我是否理解为什么异步调用会阻止 UI 线程;我将不得不做更多的研究。

我已经尝试过后台线程,但在我在任务上实施继续之前,这并没有解决我的问题。

谢谢你的帮助。

这是最终代码。

public partial class Splash : Window
{
    private TypeBO boType = new TypeBO();
    private Stopwatch swTotal = new Stopwatch();
    private Stopwatch swData= new Stopwatch();
    public Splash()
    {
        InitializeComponent();
        swTotal.Start();
    }
    private void GetData()
    {
        Assembly assembly = Assembly.GetExecutingAssembly();
        lblMessage.Content = "Please Wait...";
        lblVersion.Content = "Version: " + assembly.GetName().Version.Major + "." + assembly.GetName().Version.Minor + "." + assembly.GetName().Version.Revision;
        swData.Start();
        var backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += this.OnBackgroundWorkerDoWork;
        backgroundWorker.RunWorkerCompleted += OnBackgroundWorkerRunWorkerCompleted;
        backgroundWorker.RunWorkerAsync();
    }
    private void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
    {
        WEBAPI.GetHttpClient().GetAsync("api/COStateType/GetStateTypesByCountryID/"
            + Constants.COUNTRY_ID_USA).ContinueWith(r =>
            {
                r.Result.EnsureSuccessStatusCode();
                e.Result = r.Result.Content.ReadAsAsync<List<coStateType>>().Result;
            }).Wait();
        swData.Stop();
        System.Diagnostics.Debug.Write("Splash - DATA - Total Elapsed Time - " + swData.Elapsed + Environment.NewLine);
    }
    private void OnBackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        var backgroundWorker = sender as BackgroundWorker;
        backgroundWorker.DoWork -= this.OnBackgroundWorkerDoWork;
        backgroundWorker.RunWorkerCompleted -= OnBackgroundWorkerRunWorkerCompleted;
        App.stateTypes = e.Result as List<coStateType>;
        coStateType pleaseSelectStateType = new coStateType() { State_ID = 0, State_Nm = "Select One", Country_ID = 1 };
        App.stateTypes.Insert(0, pleaseSelectStateType);
        sbHideState.Completed += (snd, eva) =>
        {
            //Pre-Load User Controls
            Assembly assembly = this.GetType().Assembly;
            UserControl ucBHSearch = (UserControl)assembly.CreateInstance(string.Format("{0}.BHSearch", "ABS2.WPF.ADMIN.UserControls.BusinessHierarchy"));
            UserControl ucBHDetails = (UserControl)assembly.CreateInstance(string.Format("{0}.BHDetails", "ABS2.WPF.ADMIN.UserControls.BusinessHierarchy"));
            App.userControls.Add("BHD", ucBHDetails);
            App.userControls.Add("BHS", ucBHSearch);
            Login winLogin = new Login();
            winLogin.Show();
            swTotal.Stop();
            System.Diagnostics.Debug.Write("Splash - UI - Total Elapsed Time - " + swTotal.Elapsed + Environment.NewLine + Environment.NewLine);
            swTotal.Reset();
            this.Close();
        };
        sbHideState.Begin(gridSplash);
    }
    private void Window_Loaded_1(object sender, RoutedEventArgs e)
    {
        lblMessage.Content = "";
        lblVersion.Content = "";
        sbDefaultState.Begin(gridSplash);
        sbRotateState.Begin(gridSplash);           
    }
    private void window_ContentRendered(object sender, EventArgs e)
    {
        sbShowState.Completed += (snd, eva) =>
        {
            GetData();
        };
        sbShowState.Begin(gridSplash);
    }
}
你需要

使用"Task"函数来调用Async。 这不会阻止您的 GUI 主线程,您也不需要使用后台工作线程或线程。"任务"、"异步"、"等待"一起进行。

当 await 完成等待时,在原始上下文(线程)中继续执行,在您的情况下是 UI 线程。

这允许代码在不使用调度程序的情况下访问 UI 控件。这意味着最后一个等待之后的任何昂贵代码实际上都将在 UI 线程上运行。

如果希望代码继续在单独的线程上运行,则应添加 。ConfigureAwait(false) 在调用后等待,即。

await response.Content.ReadAsAsync<List<coStateType>>().ConfigureAwait(false);

在这种情况下,您应该注意使用调度程序或同步上下文方法发布/发送来访问 UI 控件,例如。

SynchronizationContext.Current.Post(_=>progressBar.Value+=5,null);

您发布的所有代码都在 UI 线程中执行。异步功能可帮助您在 UI 线程无事时(在 Web 请求期间)不阻塞它,但是如果您让 UI 线程忙于过多的代码,您的动画将始终出现问题。

我会尝试将所有与 Web 相关的代码移动到后台工作线程中,并仅向 UI 线程发布简短的 UI 更新。由于您已经尝试了后台工作线程,因此我必须假设 UI 更新需要太长时间。

如果您有性能探查器,请分析 UI 线程的繁忙时间以获得更好的图片。

最新更新