Starting Multiple Async Tasks and Process Them As They Complete (C#)

感情迁移 提交于 2020-01-04 06:15:28

问题


So I am trying to learn how to write asynchronous methods and have been banging my head to get asynchronous calls to work. What always seems to happen is the code hangs on "await" instruction until it eventually seems to time out and crash the loading form in the same method with it.

There are two main reason this is strange:

  1. The code works flawlessly when not asynchronous and just a simple loop
  2. I copied the MSDN code almost verbatim to convert the code to asynchronous calls here: https://msdn.microsoft.com/en-us/library/mt674889.aspx

I know there are a lot of questions already about this on the forms but I have gone through most of them and tried a lot of other ways (with the same result) and now seem to think something is fundamentally wrong after MSDN code wasn't working.

Here is the main method that is called by a background worker:

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

And here is the async method for querying web pages:

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

I could really use some help. Thanks!


回答1:


In short when you combine async with any 'regular' task functions you get a deadlock

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

the solution is by using configureawait

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

The reason you need this is because you didn't await your orginal thread.

// ***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]);

What's happeneing here is that you mix the await paradigm with thre regular task handling paradigm. and those don't mix (or rather you have to use the ConfigureAwait(false) for this to work.



来源:https://stackoverflow.com/questions/37398050/starting-multiple-async-tasks-and-process-them-as-they-complete-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!