簡體   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