启动多个异步任务并在完成时对其进行处理(C#) [英] Starting Multiple Async Tasks and Process Them As They Complete (C#)

查看:69
本文介绍了启动多个异步任务并在完成时对其进行处理(C#)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我正在尝试学习如何编写异步方法,并且一直在努力使异步调用起作用.似乎总是会发生的是,代码会挂在等待"指令上,直到最终看起来超时并使用相同的方法使加载表单崩溃.

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. 当代码不是异步的并且只是一个简单的循环时,代码可以完美地工作
  2. 我几乎逐字复制了MSDN代码,以将代码转换为异步调用: https://msdn.microsoft.com/zh-CN/library/mt674889.aspx

我知道表单上已经有很多关于此的问题,但是我已经遍历了大多数问题,并尝试了许多其他方法(具有相同的结果),现在看来,在MSDN代码之后,某些事情根本上是错误的没用.

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!

推荐答案

简而言之,当您将异步与任何常规"任务功能结合使用时,就会出现死锁

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/

解决方案是使用configureawait

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

这里发生的是,您将等待模式与常规任务处理模式混合在一起.而那些不会混合使用(或者,您必须使用ConfigureAwait(false)才能使它正常工作.

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.

这篇关于启动多个异步任务并在完成时对其进行处理(C#)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆