2016-05-23 84 views
2

因此,我正在嘗試學習如何編寫異步方法,並且一直在敲我的頭以使異步調用工作。總是會發生這樣的情況:代碼掛在「等待」指令上,直到它最終似乎超時並以與其相同的方法崩潰加載表單。啓動多個異步任務並處理它們(C#)

主要有兩個原因,這是奇怪的:

  1. 的代碼完美的作品時,沒有異步的,只是一個簡單的循環
  2. 我複製了MSDN代碼幾乎是逐字的代碼轉換爲這裏異步調用: https://msdn.microsoft.com/en-us/library/mt674889.aspx

我知道有很多已經對這個表格上的問題,但我已經完成大部分的消失了,嘗試了很多其他的方法(同樣的結果),現在似乎在思考着什麼是根本MSDN代碼無法工作後出現錯誤。

這裏是由後臺工作人員稱主要方法:

// this method loads data from each individual webPage 
async Task LoadSymbolData(DoWorkEventArgs _e) 
{ 
    int MAX_THREADS = 10; 
    int tskCntrTtl = dataGridView1.Rows.Count; 
    Dictionary<string, string> newData_d = new Dictionary<string, string>(tskCntrTtl); 
    // we need to make copies of things that can change in a different thread 
    List<string> links = new List<string>(dataGridView1.Rows.Cast<DataGridViewRow>() 
     .Select(r => r.Cells[dbIndexs_s.url].Value.ToString()).ToList()); 
    List<string> symbols = new List<string>(dataGridView1.Rows.Cast<DataGridViewRow>() 
     .Select(r => r.Cells[dbIndexs_s.symbol].Value.ToString()).ToList()); 
    // we need to create a cancelation token once this is working 
    // TODO 

    using (LoadScreen loadScreen = new LoadScreen("Querying stock servers...")) 
    { 
     // we cant use the delegate becaus of async keywords 
     this.loaderScreens.Add(loadScreen); 
     // wait until the form is loaded so we dont get exceptions when writing to controls on that form 
     while (!loadScreen.IsLoaded()); 
     // load the total number of operations so we can simplify incrementing the progress bar 
     // on seperate form instances 
     loadScreen.LoadProgressCntr(0, tskCntrTtl); 
     // try to run all async tasks since they are non-blocking threaded operations 
     for (int i = 0; i < tskCntrTtl; i += MAX_THREADS) 
     { 
      List<Task<string[]>> ProcessURL = new List<Task<string[]>>(); 
      List<int> taskList = new List<int>(); 

      // Make a list of task indexs 
      for (int task = i; task < i + MAX_THREADS && task < tskCntrTtl; task++) 
       taskList.Add(task); 

      // ***Create a query that, when executed, returns a collection of tasks. 
      IEnumerable<Task<string[]>> downloadTasksQuery = 
       from task in taskList select QueryHtml(loadScreen, links[task], symbols[task]); 

      // ***Use ToList to execute the query and start the tasks. 
      List<Task<string[]>> downloadTasks = downloadTasksQuery.ToList(); 

      // ***Add a loop to process the tasks one at a time until none remain. 
      while (downloadTasks.Count > 0) 
      { 
       // Identify the first task that completes. 
       Task<string[]> firstFinishedTask = await Task.WhenAny(downloadTasks); // <---- CODE HANGS HERE 

       // ***Remove the selected task from the list so that you don't 
       // process it more than once. 
       downloadTasks.Remove(firstFinishedTask); 

       // Await the completed task. 
       string[] data = await firstFinishedTask; 
       if (!newData_d.ContainsKey(data.First())) 
        newData_d.Add(data.First(), data.Last()); 
      } 
     } 
     // now we have the dictionary with all the information gathered from teh websites 
     // now we can add the columns if they dont already exist and load the information 
     // TODO 
     loadScreen.UpdateProgress(100); 
     this.loaderScreens.Remove(loadScreen); 
    } 
} 

這裏是查詢網頁上的異步方法:

async Task<string[]> QueryHtml(LoadScreen _loadScreen, string _link, string _symbol) 
{ 
    string data = String.Empty; 

    try 
    { 
     HttpClient client = new HttpClient(); 
     var doc = new HtmlAgilityPack.HtmlDocument(); 
     var html = await client.GetStringAsync(_link); // <---- CODE HANGS HERE 
     doc.LoadHtml(html); 

     string percGrn = doc.FindInnerHtml(
      "//span[contains(@class,'time_rtq_content') and contains(@class,'up_g')]//span[2]"); 
     string percRed = doc.FindInnerHtml(
      "//span[contains(@class,'time_rtq_content') and contains(@class,'down_r')]//span[2]"); 

     // create somthing we'll nuderstand later 
     if ((String.IsNullOrEmpty(percGrn) && String.IsNullOrEmpty(percRed)) || 
      (!String.IsNullOrEmpty(percGrn) && !String.IsNullOrEmpty(percRed))) 
      throw new Exception(); 

     // adding string to empty gives string 
     string perc = percGrn + percRed; 
     bool isNegative = String.IsNullOrEmpty(percGrn); 
     double percDouble; 

     if (double.TryParse(Regex.Match(perc, @"\d+([.])?(\d+)?").Value, out percDouble)) 
      data = (isNegative ? 0 - percDouble : percDouble).ToString(); 
    } 
    catch (Exception ex) { } 
    finally 
    { 
     // update the progress bar... 
     _loadScreen.IncProgressCntr(); 
    } 

    return new string[] { _symbol, data }; 
} 

我真的可以使用一些幫助。謝謝!

+1

爲什麼要將它包裝到BackgroundWorker中?只要您等待LoadSymbolData ' –

+0

我不確定你是什麼意思,後臺工作者在一個單獨的線程上運行,這不同於只是異步。 – user2368363

+1

一個'BackgroundWorker'在另一個線程上工作,是的,你可能想這樣做,如果UI線程不應該被阻塞,因爲這會導致你的UI組件凍結。 'async' -'await'增加了一種異步執行代碼的新方法,這樣應用程序**不會被阻止。只要你等待你的'Task',它就會異步運行(如果它也在內部等待另一個方法或者運行一個新的Task),那麼就不需要另一個線程。 –

回答

1

總之當你把異步與任何「常規」任務功能,你得到一個僵局

http://olitee.com/2015/01/c-async-await-common-deadlock-scenario/

的解決方案是使用configureawait

var html = await client.GetStringAsync(_link).ConfigureAwait(false); 

的原因,你需要這是因爲你沒有等待你的原始線程。

// ***Create a query that, when executed, returns a collection of tasks. 
IEnumerable<Task<string[]>> downloadTasksQuery = from task in taskList select QueryHtml(loadScreen,links[task], symbols[task]); 

這裏發生了什麼是您將await範例與常規任務處理範例混合使用。並且不會混合使用(或者你必須使用ConfigureAwait(false)才能工作

+0

我在這方面還很新,所以我將不得不做一些研究,以確定你的意思。我必須將ConfigureAwait(false)設置爲你提到的兩個,也是任務 firstFinishedTask = await Task.WhenAny(downloadTasks) – user2368363

+0

由於某種原因,儘管我的LoadScreen在一段時間後消失了。它抱怨與它交叉線程通信(即使它在大多數方法中調用它),所以我繼續前進並將其從QueryHtml()方法中拉出,並且在線程數量MAX_THREADS之後從LoadSymbolData()中調用它完成。但它仍然消失......在逐步完成之後,它似乎仍然在經歷這些方法並擁有所有正確的信息。它似乎跨越了調用部分,但... – user2368363