启动多个异步任务并在完成时处理它们(C#)

时间:2016-05-23 18:29:29

标签: c# .net asynchronous html-agility-pack

所以我正在努力学习如何编写异步方法,并且一直在努力让异步调用工作。似乎总是发生的事情是代码挂起“await”指令,直到它最终似乎超时并使用相同的方法崩溃加载表单。

这有两个主要原因:

  1. 代码在非异步且只是一个简单的循环
  2. 时可以完美运行
  3. 我几乎逐字地复制了MSDN代码,将代码转换为异步调用:https://msdn.microsoft.com/en-us/library/mt674889.aspx
  4. 我知道在表单上已经有很多关于这方面的问题,但我已经完成了大部分问题并尝试了很多其他方法(结果相同),现在似乎认为在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 个答案:

答案 0 :(得分: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范例与thre常规任务处理范例混合在一起。那些不混合(或者你必须使用ConfigureAwait(false)才能工作。