简体   繁体   English

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

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

I have been reasearching once again the async tasks. 我一直在重新研究异步任务。 No mater how i set the Tasks, my application suffers from UI freeze all the time. 无论如何设置任务,我的应用程序一直都遭受UI冻结的困扰。 I have the following code for downloading the string from a webpage: 我有以下代码用于从网页下载字符串:

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;

}

When i execute this task and during the for loop the UI of my application is freezing. 当我执行此任务时,在for循环期间,我的应用程序的UI冻结。 Even though i believe that this code should not freeze the UI i am not able to find a solution. 即使我认为此代码不应冻结UI,也无法找到解决方案。 I have tried many different options and really want to use tasks instead of threads or events with webclient async. 我尝试了许多不同的选项,并且真的想使用任务而不是线程或带有webclient异步事件的事件。

Info: I am using .net 4.5 for my project. 信息:我正在为项目使用.net 4.5。 The difference in my code is that these functions are inside a class library(don't know if it matters). 我的代码的不同之处在于这些函数位于类库中(不知道是否重要)。

Is it possible to run this code without blocking the user interface with async await by calling the DownloadString function from my code? 是否可以通过从我的代码中调用DownloadString函数来运行此代码而不会阻塞异步等待的用户界面? If not what are the alternatives(any good nuget packages)? 如果不是,有什么替代方法(任何好的nuget包)?

The async keyword doesn't make something run asynchronously, it enables you to use await to await an already asynchronous operation. async关键字不会使某些内容异步运行,它使您可以使用await等待已经异步的操作。 You need to use DownloadStringTaskAsync to truly download in an asynchronous manner: 您需要使用DownloadStringTaskAsync以异步方式真正下载:

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

await by itself returns execution in the original execution context (ie the UI thread). await本身将在原始执行上下文(即UI线程)中返回执行。 This may or may not be desirable, which is why library code typically uses ConfigureAwait(false); 这可能是不希望的,这就是为什么库代码通常使用ConfigureAwait(false); and lets the final user of the library to decide how to await: 并让图书馆的最终用户决定如何等待:

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

Finally, there's no point in awaiting if you are going to call .Result from the top-level function. 最后,没有必要等待从顶级函数调用.Result There is no point in using await at all if you don't want to do use the method's result in your code. 如果您不想在代码中使用该方法的结果,那么根本就不需要使用await LoadCompanyContracts could be just: LoadCompanyContracts可能只是:

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

Oops 哎呀

Typically, you don't need to use await at all if you just return the result of an asynchronous operation. 通常,如果只返回异步操作的结果,则根本不需要使用await。 The method could just return wc.DownloadStringTaskAsync(..); 该方法可以只return wc.DownloadStringTaskAsync(..); BUT that would cause the method to return and dispose the WebClient before download finishes. 但是可能导致方法在下载完成之前返回处理WebClient Avoiding the using block isn't a solution either, as it will let an expensive object like WebClient alive longer than necessary. 避免using块也不是解决方案,因为它会使像WebClient这样的昂贵对象的存活时间比必要的更长。

That's why HttpClient is preferable to WebClient : a single instance supports multiple concurrent calls, which means you can have just one instance eg as a field and reuse it, eg: 这就是HttpClient优于WebClient的原因:单个实例支持多个并发调用,这意味着您只能将一个实例(例如)用作字段并重用它,例如:

  HttpClient _myClient =new HttpClient();

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

You could get rid of your DownloadString since it doesn't do anything on top of LoadCompanyContracts . 您可以摆脱DownloadString因为它在LoadCompanyContracts之上不做任何事情。 If it does use the result of LoadCompanyContracts , it should be rewritten as: 如果确实使用LoadCompanyContracts的结果,则应将其重写为:

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

EDIT 编辑

The original answer used DownloadStringAsync which is a legacy method that raises an event when download completes. 原始答案使用DownloadStringAsync ,这是一个旧方法,当下载完成时会引发一个事件。 The correct method is DownloadStringTaskAsync 正确的方法是DownloadStringTaskAsync

EDIT 2 Since we are talking about a UI, the code can be made asynchronous all the way to the top event handler by using the async void syntax for the handler, eg async void Button1_Click , eg: 编辑2既然我们在谈论UI, 可以通过使用处理程序的async void语法,例如async void Button1_Click ,使代码一直到顶级事件处理程序一直async void Button1_Click

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

In this case we want to return to the original thread, so we don't use ConfigureAwait(false); 在这种情况下,我们返回到原始线程,因此我们不使用ConfigureAwait(false);

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

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