简体   繁体   中英

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. 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. Even though i believe that this code should not freeze the UI i am not able to find a solution. I have tried many different options and really want to use tasks instead of threads or events with webclient async.

Info: I am using .net 4.5 for my project. 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? If not what are the alternatives(any good nuget packages)?

The async keyword doesn't make something run asynchronously, it enables you to use await to await an already asynchronous operation. You need to use DownloadStringTaskAsync to truly download in an asynchronous manner:

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). This may or may not be desirable, which is why library code typically uses 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. There is no point in using await at all if you don't want to do use the method's result in your code. LoadCompanyContracts could be just:

 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. The method could just return wc.DownloadStringTaskAsync(..); BUT that would cause the method to return and dispose the WebClient before download finishes. Avoiding the using block isn't a solution either, as it will let an expensive object like WebClient alive longer than necessary.

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 _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 . If it does use the result of LoadCompanyContracts , it should be rewritten as:

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. The correct method is 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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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