繁体   English   中英

异步/等待,在没有明显原因的情况下阻止用户界面

[英]Async/await, run blocking the UI without obvious reason

我一直在重新研究异步任务。 无论如何设置任务,我的应用程序一直都遭受UI冻结的困扰。 我有以下代码用于从网页下载字符串:

internal string DownloadString(string URL)
{
      var result =  LoadCompanyContracts(URL);
      return result.Result;
}

internal async Task<string> LoadCompanyContracts(string URL)
{
Task<string> task2 = Task<string>.Factory.StartNew(() =>
{
     for (int i = 0; i <= 10000000; i++) Console.WriteLine(i);
     WebClient wc = new WebClient();
     string tmp = wc.DownloadString(new Uri(URL));
     return tmp;
});
return task2.Result;

}

当我执行此任务时,在for循环期间,我的应用程序的UI冻结。 即使我认为此代码不应冻结UI,也无法找到解决方案。 我尝试了许多不同的选项,并且真的想使用任务而不是线程或带有webclient异步事件的事件。

信息:我正在为项目使用.net 4.5。 我的代码的不同之处在于这些函数位于类库中(不知道是否重要)。

是否可以通过从我的代码中调用DownloadString函数来运行此代码而不会阻塞异步等待的用户界面? 如果不是,有什么替代方法(任何好的nuget包)?

async关键字不会使某些内容异步运行,它使您可以使用await等待已经异步的操作。 您需要使用DownloadStringTaskAsync以异步方式真正下载:

internal async Task<string> LoadCompanyContracts(string URL)
{
    ....
    using(var wc = new WebClient())
    {
        string tmp = await wc.DownloadStringTaskAsync(new Uri(URL));
        return tmp;
    }
}

await本身将在原始执行上下文(即UI线程)中返回执行。 这可能是不希望的,这就是为什么库代码通常使用ConfigureAwait(false); 并让图书馆的最终用户决定如何等待:

string tmp = await wc.DownloadStringTaskAsync(new Uri(URL))
                     .ConfigureAwait(false);

最后,没有必要等待从顶级函数调用.Result 如果您不想在代码中使用该方法的结果,那么根本就不需要使用await LoadCompanyContracts可能只是:

 internal Task<string> LoadCompanyContracts(string URL) { .... using(var wc = new WebClient()) { return wc.DownloadStringTaskAsync(new Uri(URL)) .ConfigureAwait(false); } } 

哎呀

通常,如果只返回异步操作的结果,则根本不需要使用await。 该方法可以只return wc.DownloadStringTaskAsync(..); 但是可能导致方法在下载完成之前返回处理WebClient 避免using块也不是解决方案,因为它会使像WebClient这样的昂贵对象的存活时间比必要的更长。

这就是HttpClient优于WebClient的原因:单个实例支持多个并发调用,这意味着您只能将一个实例(例如)用作字段并重用它,例如:

  HttpClient _myClient =new HttpClient();

  internal Task<string> LoadCompanyContractsAsync(string URL)
  {
      ....
      return _myClient.GetStringAsync(new Uri(URL))
                      .ConfigureAwait(false);
  }
}

您可以摆脱DownloadString因为它在LoadCompanyContracts之上不做任何事情。 如果确实使用LoadCompanyContracts的结果,则应将其重写为:

internal async Task<string> DownloadString(string URL)
{
  var result =  await LoadCompanyContracts(URL);
  //Do something with the result
  return result;
}

编辑

原始答案使用DownloadStringAsync ,这是一个旧方法,当下载完成时会引发一个事件。 正确的方法是DownloadStringTaskAsync

编辑2既然我们在谈论UI, 可以通过使用处理程序的async void语法,例如async void Button1_Click ,使代码一直到顶级事件处理程序一直async void Button1_Click

async void LoadCustomers_Click(...)
{   
    var contracts=await LoaCompanyContracts(_companyUrls);
    txtContracts>Text=contracts;
}

在这种情况下,我们返回到原始线程,因此我们不使用ConfigureAwait(false);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM