如何在不运行它们的情况下合并两个Linq IEnumerable <t>查询?</t>

时间:2012-12-01 20:32:04

标签: linq task-parallel-library ienumerable

如何合并List<T>基于TPL的任务以便以后执行?

 public async IEnumerable<Task<string>> CreateTasks(){ /* stuff*/ }

我的假设是.Concat() ......

     void MainTestApp()  // Full sample available upon request.
     {
        List<string> nothingList = new List<string>();
        nothingList.Add("whatever");
        cts = new CancellationTokenSource();

         delayedExecution =
            from str in nothingList
            select AccessTheWebAsync("", cts.Token);
         delayedExecution2 =
          from str in nothingList
          select AccessTheWebAsync("1", cts.Token);

         delayedExecution = delayedExecution.Concat(delayedExecution2);
     }


    /// SNIP

    async Task AccessTheWebAsync(string nothing, CancellationToken ct)
    {
        // return a Task
    }

我想确保这不会产生任何任务或评估任何内容。事实上,我想我在问“什么在逻辑上执行IQueryable到返回数据的东西”?

背景

由于我正在进行递归,并且我不希望在正确的时间之前执行此操作,如果多次调用合并结果的正确方法是什么?

如果重要,我正在考虑运行此命令来启动此代码后面的所有任务var AllRunningDataTasks = results.ToList();

while (AllRunningDataTasks.Count > 0)
{
    // Identify the first task that completes.
    Task<TableResult> firstFinishedTask = await Task.WhenAny(AllRunningDataTasks);

    // ***Remove the selected task from the list so that you don't
    // process it more than once.
    AllRunningDataTasks.Remove(firstFinishedTask);

    // TODO: Await the completed task.
    var taskOfTableResult = await firstFinishedTask;

    // Todo: (doen't work)
    TrustState thisState = (TrustState)firstFinishedTask.AsyncState;

    // TODO: Update the concurrent dictionary with data
    // thisState.QueryStartPoint + thisState.ThingToSearchFor 

    Interlocked.Decrement(ref thisState.RunningDirectQueries);
    Interlocked.Increment(ref thisState.CompletedDirectQueries);

    if (thisState.RunningDirectQueries == 0)
    {
        thisState.TimeCompleted = DateTime.UtcNow;
    }
}

2 个答案:

答案 0 :(得分:2)

回答具体问题“逻辑上执行IQueryable到返回数据的东西”?这将是强制产生至少一个价值的任何因素,或强制发现价值是否可用。

例如,ToListToArrayFirstSingleSingleOrDefaultCount都会强制进行评估。 (虽然First不会评估整个集合 - 它会检索第一个项目然后停止。)这些都必须至少开始检索值,因为它们中的任何一个都无法返回它们返回的内容这样做。在ToListToArray的情况下,这些返回完全填充的非惰性集合,这就是他们必须评估所有内容的原因。返回单个项目的方法至少需要请求第一个项目,然后Single将继续检查如果评估继续进行则不会出现任何其他内容(如果结果是,则抛出异常)更多)。

使用foreach迭代查询也会强制进行评估。 (同样,这也是出于同样的原因:你要求它从集合中获取实际值,因此它必须提供它们。)

Concat不会立即进行评估,因为它不需要 - 只有当您向连接序列询问需要询问其输入值的值时才会这样。

顺便说一下,虽然你问过IQueryable你在这里的例子中没有使用它。这可能很重要,因为与实际获得的LINQ to Objects实现(您可以获得普通IEnumerable<T>)相比,它的工作方式存在一些差异。我不认为它在这个例子中有所作为,但它让我想知道你的原始代码和你发布的插图版本之间是否有某些变化?这很重要,因为不同的LINQ提供商可以采用不同的方式。 IEnumerable<T> Concat的{​​{1}}风格肯定会使用延迟评估,虽然我希望大多数其他LINQ实现都是如此,但它并不是绝对的。

如果您需要多次使用结果,并且您希望确保只评估它们一次,但是在实际需要它们之前不进行评估,那么通常的方法是调用{{1}在您肯定需要评估的位置,然后保持生成的ToList,以便您可以再次使用它。获得List<T>(或数组)表单中的数据后,您可以根据需要多次使用该列表。

顺便说一句,你的第一个问题有一个问题:

“如何合并基于TPL的任务列表以便以后执行?”

通常,如果您已经有TPL任务,那么就无法阻止它执行。 (这是一个例外。如果你直接构造一个List<T>而不是使用一种更普通的方法来创建它,它会在你告诉它之前实际运行。但是一般来说,返回的API任务返回实时任务,即,当你接触它们时,它们可能已经在运行,甚至完成。)

您示例中的“稍后执行”来自于您实际上根本没有完成任务列表的事实。 (如果你确实有Task个任务,那么“以后的执行”将不是一个选项。)你所拥有的是几个枚举,如果你要评估它们,就会创建任务。创建任务的行为与在任何返回任务的TAP样式API中启动它的行为是不可分割的。

根据你写的其他内容,我认为你真正想要的是:

“如何将多个List<T>对象合并为一个IEnumerable<Task<T>>,其方式是推迟对基础枚举的评估,直到评估组合的枚举本身为止?”

IEnumerable<Task<T>>应该适用于此。

答案 1 :(得分:0)

以下是一种将数据合并的hacky方式...我不喜欢在Main或本示例的其他几个方面使用“nothingList”,但它似乎完成了工作并允许我要合并待处理的任务。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

// Add a using directive and a reference for System.Net.Http.
using System.Net.Http;

// Add the following using directive.
using System.Threading;


namespace ProcessTasksAsTheyFinish
{
    public partial class MainWindow : Window
    {
        // Declare a System.Threading.CancellationTokenSource.
        CancellationTokenSource cts;
        List<IEnumerable<Task>> launchList = new List<IEnumerable<Task>>();

        public MainWindow()
        {
            InitializeComponent();

            List<string> nothingList = new List<string>();
            nothingList.Add("whatever");

            cts = new CancellationTokenSource();

             delayedExecution =
                from str in nothingList
                select AccessTheWebAsync("", cts.Token);


             List<string> nothingList2 = new List<string>();
             nothingList2.Add("whatever");

             delayedExecution2 =
              from str in nothingList2
              select AccessTheWebAsync("1", cts.Token);


             launchList.Add(delayedExecution);
             launchList.Add(delayedExecution2);

             delayedExecution = delayedExecution.Concat(delayedExecution2);
        }
        IEnumerable<Task> delayedExecution = null;
        IEnumerable<Task> delayedExecution2 = null;

        private async void startButton_Click(object sender, RoutedEventArgs e)
        {
            resultsTextBox.Clear();

            // Instantiate the CancellationTokenSource.

            try
            {
                // ***Set up the CancellationTokenSource to cancel after 25 seconds.
                //cts.CancelAfter(250000);

                var test  =  delayedExecution;// AccessTheWebAsync("", cts.Token);

                var testList = test.ToList();

                while (testList.Count() > 0)
                {
                    var firstFinishedTask = await Task.WhenAny(testList);
                    testList.Remove(firstFinishedTask);

                      await firstFinishedTask;
                }

                resultsTextBox.Text += "\r\nDownloads complete.";
            }
            catch (OperationCanceledException tee)
            {
                resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
            }
            catch (Exception)
            {
                resultsTextBox.Text += "\r\nDownloads failed.\r\n";
            }

            cts = null;
        }


        private void cancelButton_Click(object sender, RoutedEventArgs e)
        {
            if (cts != null)
            {
                cts.Cancel();
            }
        }


        async Task<string> AccessTheWebAsync(string nothing, CancellationToken ct)
        {
            // CHANGE THIS VALUE TO CONTROL THE TESTING
            bool delayConversionOfQueryToList = false;

            HttpClient client = new HttpClient();

            // Make a list of web addresses.
            List<string> urlList = null;

            if (nothing == "1")
            {
                urlList = SetUpURLList2();
            }
            else urlList = SetUpURLList();

            // ***Create a query that, when executed, returns a collection of tasks.
            IEnumerable<Task<int>> downloadTasksQuery =
                from url in urlList select ProcessURL(url, client, ct);

            // DEBUG!!!
            if (delayConversionOfQueryToList == true)
            {
                await Task.Delay(10000);
                resultsTextBox.Text += String.Format("\r\nDelay of IQueryable complete.  Tip: Did you see any IsRunning messages?");
            }

            // ***Use ToList to execute the query and start the tasks. 
            List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

            // DEBUG!!!
            if (delayConversionOfQueryToList == false)
            {
                await Task.Delay(10000);
                resultsTextBox.Text += String.Format("\r\nDelay of .ToList() complete.   Tip: Did you see any IsRunning messages?");
            }

            // ***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<int> firstFinishedTask = await Task.WhenAny(downloadTasks);

                resultsTextBox.Text += String.Format("\r\nID  {0}", firstFinishedTask.Id);

                // ***Remove the selected task from the list so that you don't
                // process it more than once.
                downloadTasks.Remove(firstFinishedTask);

                // Await the completed task.
                int length = await firstFinishedTask;
                resultsTextBox.Text += String.Format("\r\nLength of the download:  {0}", length);
            }

            return nothing;
        }


        private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "http://msdn.microsoft.com",
                "http://msdn.microsoft.com/library/windows/apps/br211380.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }
        private List<string> SetUpURLList2()
        {
            List<string> urls = new List<string> 
            { 
                "http://www.google.com",

            };
            return urls;
        }

        async Task<int> ProcessURL(string url, HttpClient client, CancellationToken ct)
        {
            resultsTextBox.Text += String.Format("\r\nIS RUNNING {0}", url);

            // GetAsync returns a Task<HttpResponseMessage>. 
            HttpResponseMessage response = await client.GetAsync(url, ct);
            // Retrieve the website contents from the HttpResponseMessage.
            byte[] urlContents = await response.Content.ReadAsByteArrayAsync();

           // Thread.Sleep(3000);
           // await Task.Delay(1000, ct);
           return urlContents.Length;
        }
    }
}

// Sample Output:

IS RUNNING http://msdn.microsoft.com
IS RUNNING http://msdn.microsoft.com/library/windows/apps/br211380.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/hh290136.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/dd470362.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/aa578028.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/ms404677.aspx
IS RUNNING http://msdn.microsoft.com/en-us/library/ff730837.aspx
IS RUNNING http://www.google.com
Delay of .ToList() complete.   Tip: Did you see any IsRunning messages?
ID  1
Length of the download:  48933
ID  2
Length of the download:  375328
ID  3
Length of the download:  220428
ID  4
Length of the download:  222256
ID  5
Length of the download:  229330
ID  6
Length of the download:  136544
ID  7
Length of the download:  207171
Delay of .ToList() complete.   Tip: Did you see any IsRunning messages?
ID  8
Length of the download:  43945
Downloads complete.