C#中的循环异步任务列表

时间:2014-06-03 16:52:46

标签: c# asynchronous async-await

我正在尝试不断地解析来自多个网站的数据。我希望以异步方式在循环中单独执行此操作,直到程序关闭。我不确定这种逻辑应该是什么结构。

现在我正在遵循这种模式。

async public void ParseAll(List<Site> SiteList)
{
    List<Task> TaskList = new List<Task>();

    foreach(Site s in SiteList)
    {
        TaskList.Add(s.ParseData);
    }

    await Task.WhenAll(TaskList)
}

问题在于,如果我围绕此方法构造一个循环,那么首先更新的站点必须等到整个列表完成才能再次运行该方法。从理论上讲,我想做的就是在完成TaskList方法时将每个网站放回ParseData的底部,但我不确定这是否可行,或者这是否是最好的方法。

4 个答案:

答案 0 :(得分:3)

  

从理论上讲,我想做的就是把每个网站都放回去   完成ParseData

后,TaskList的底部

看起来您需要维护要处理的网站队列。以下是我对此的看法,使用SemaphoreSlim。这样,您还可以将并发任务的数量限制为小于实际的站点数,或者即时添加新站点。 CancellationToken用于从外部停止处理。在{IMO}使用async void是合理的,QueueSiteAsync会跟踪它开始的任务。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncLoop
{
    class Program
    {
        public class Site
        {
            public string Url { get; set; }
            public async Task ParseDataAsync(CancellationToken token)
            {
                // simulate download and parse
                int delay = new Random(Environment.TickCount).Next(100, 1000);
                await Task.Delay(delay, token);
                Console.WriteLine("Processed: #{0}, delay: {1}", this.Url, delay);
            }
        }

        object _lock = new Object();
        HashSet<Task> _pending = new HashSet<Task>(); // sites in progress
        SemaphoreSlim _semaphore;

        async void QueueSiteAsync(Site site, CancellationToken token)
        {
            Func<Task> processSiteAsync = async () =>
            {
                await _semaphore.WaitAsync(token).ConfigureAwait(false);
                try 
                {           
                    await site.ParseDataAsync(token);
                    QueueSiteAsync(site, token);
                }
                finally
                {
                    _semaphore.Release();
                }
            };

            var task = processSiteAsync();
            lock (_lock)
                _pending.Add(task);
            try
            {
                await task;
                lock (_lock)
                    _pending.Remove(task);
            }
            catch
            {
                if (!task.IsCanceled && !task.IsFaulted)
                    throw; // non-task error, re-throw

                // leave the faulted task in the pending list and exit
                // ProcessAllSites will pick it up
            }
        }

        public async Task ProcessAllSites(
            Site[] sites, int maxParallel, CancellationToken token)
        {
            _semaphore = new SemaphoreSlim(Math.Min(sites.Length, maxParallel));

            // start all sites
            foreach (var site in sites)
                QueueSiteAsync(site, token);

            // wait for cancellation
            try
            {
                await Task.Delay(Timeout.Infinite, token);
            }
            catch (OperationCanceledException)
            {
            }

            // wait for pending tasks
            Task[] tasks;
            lock (_lock)
                tasks = _pending.ToArray();
            await Task.WhenAll(tasks);
        }

        // testing
        static void Main(string[] args)
        {
            // cancel processing in 10s
            var cts = new CancellationTokenSource(millisecondsDelay: 10000); 
            var sites = Enumerable.Range(0, count: 10).Select(i => 
                new Site { Url = i.ToString() });
            try
            {
                new Program().ProcessAllSites(
                    sites.ToArray(), 
                    maxParallel: 5, 
                    token: cts.Token).Wait();
            }
            catch (AggregateException ex)
            {
                foreach (var innerEx in ex.InnerExceptions)
                    Console.WriteLine(innerEx.Message);
            }
        }
    }
}

您可能还想将下载和解析分开到单独的管道中,请查看this以获取更多详细信息。

答案 1 :(得分:1)

创建一个连续循环并一遍又一遍地解析单个站点的方法非常容易。拥有该方法后,您可以在列表中的每个站点上调用一次:

private async void ParseSite(Site s)
{
    while (true)
    {
        await s.ParseData();
    }
}

public void ParseAll(List<Site> siteList)
{
    foreach (var site in siteList)
    {
        ParseSite(site);
    }
}

答案 2 :(得分:0)

如果你想在完成后再次访问该网站,你可能想要使用Task.WhenAny并将你的外部循环与你的内部循环集成,就像这样(假设ParseData函数将返回Site,它是解析):

async public void ParseAll(List<Site> SiteList)
{
    while (true)
    {
        List<Task<Site>> TaskList = new List<Task<Site>>();

        foreach(Site s in SiteList)
        {
            TaskList.Add(s.ParseData());
        }

        await Task.WhenAny(TaskList);
        TaskList = TaskList.Select(t => t.IsCompleted ? t.Result.ParseData() : t).ToList();
    }
}

答案 3 :(得分:-1)

您是否尝试过PLinq lib?

Plinq允许您执行linq querys async。

在你的情况下,它看起来像:

SiteList. AsParallel()ForEach (s => s.ParseData);