【问题标题】:Starting Multiple Async Tasks and Process Them As They Complete (C#)启动多个异步任务并在它们完成时对其进行处理 (C#)
【发布时间】:2016-05-23 18:29:29
【问题描述】:

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

这很奇怪有两个主要原因:

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

我真的需要一些帮助。谢谢!

【问题讨论】:

  • 你为什么要把它包装成BackgroundWorker?只要您等待LoadSymbolData 的调用,它就已经是异步的了。
  • 我不确定你的意思,后台工作线程运行在一个单独的线程上,这与异步不同。
  • A BackgroundWorker 在另一个线程上工作,是的。如果不应该阻止 UI 线程,您可能想要这样做,因为这会导致您的 UI 组件冻结。 async-await 增加了一种异步执行代码的新方式,因此应用程序不会被阻塞。一旦您等待Task,它将异步运行(如果它还在内部等待另一个方法或运行新的Task)。不需要另一个线程。
  • 我的后台例程在抽象中是一个异步例程,运行在与主 UI 不同的线程上,因此 UI 可以处理密集的处理事件,而不会瓶颈异步等待事件。所以我把它分成两个单独的线程,这样我可以更快地接收数据并消除主 UI 的一些延迟。请记住,我已经测试并确认此应用程序存在差异。

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


【解决方案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]);

这里发生的事情是将等待范式与常规任务处理范式混合在一起。并且那些不混合(或者更确切地说,您必须使用 ConfigureAwait(false) 才能使其工作。

【讨论】:

  • 我在这方面还是很陌生,所以我必须对你的意思进行一些研究。我必须对您提到的内容以及 Task firstFinishedTask = await Task.WhenAny(downloadTasks) 进行 ConfigureAwait(false)
  • 由于某种原因,尽管我的 LoadScreen 在一段时间后消失了。它抱怨与它的跨线程通信(即使它在它的大多数方法中都调用了)所以我继续将它从 QueryHtml() 方法中拉出来,并在线程数 MAX_THREADS 之后从 LoadSymbolData() 调用它是完全的。但它仍然消失了......在逐步完成之后,它似乎仍在经历这些方法并拥有所有正确的信息。不过,它似乎越过了调用部分......
猜你喜欢
  • 1970-01-01
  • 2014-09-20
  • 2021-12-13
  • 2021-05-11
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多