【问题标题】:Tasks with number of child tasks具有子任务数量的任务
【发布时间】:2018-07-18 13:42:52
【问题描述】:

场景是这样的,我手头有 4 个特定的 URL,每个 URL 页面包含许多指向网页的链接,我需要提取这些网页的一些信息。我打算使用嵌套任务来完成这项工作,一个任务中的多个任务。如下所示。

        var t1Actions = new List<Action>();
        var t1 = Task.Factory.StartNew(() =>
            {
                foreach (var action in t1Actions)
                {
                    Task.Factory.StartNew(action, TaskCreationOptions.AttachedToParent);
                }
            });

        var t2Actions = new List<Action>();
        var t2 = Task.Factory.StartNew(() =>
            {
                foreach (var action in t2Actions)
                {
                    Task.Factory.StartNew(action, TaskCreationOptions.AttachedToParent);
                }
            });

        var t3Actions = new List<Action>();
        var t3 = Task.Factory.StartNew(() =>
            {
                foreach (var action in t3Actions)
                {
                    Task.Factory.StartNew(action, TaskCreationOptions.AttachedToParent);
                }
            });

        var t4Actions = new List<Action>();
        var t4 = Task.Factory.StartNew(() =>
            {
                foreach (var action in t4Actions)
                {
                    Task.Factory.StartNew(action, TaskCreationOptions.AttachedToParent);
                }
            });

        Task.WhenAll(t1, t2, t3, t4);

这是我的问题:

  1. 这种方式是完成我上面提到的工作的好方法吗?
  2. 哪个效率高,将子任务替换为Parallel.Invoke(action)还是保持原样?
  3. 如果嵌套任务完成,我应该如何通知(例如 UI),我是否可以控制嵌套任务?

任何建议都会有所帮助。

【问题讨论】:

  • 不,这不是一个好方法。您应该使用awaitasync API 方法从网页读取信息,例如byte[] urlContents = await client.GetByteArrayAsync(url);。 (见HttpClient.GetByteArrayAsync()
  • “这种方式是完成我上面提到的工作的好方法吗?”运行代码时发生了什么?成功了吗?
  • @Servy 我想听听专家的意见,我还没有开始。
  • @saber 然后开始。做你的研究。尝试自己解决问题。看看他们工作得如何。一旦遇到无法通过研究或自己尝试解决的问题,请在 SO 上提问。

标签: c# .net multithreading task task-parallel-library


【解决方案1】:

实际的问题不是如何处理子任务。它是如何从某些目录页面获取 URL 列表、检索这些页面并处理它们。

这可以使用 .NET 的 Dataflow 库轻松完成。每个步骤都可以实现为读取一个 URL 并产生输出的块。

  1. 第一个块可以是TransformManyBlock,它接受一个页面 URL 并返回页面 URL 列表
  2. 第二个块可以是TransformBlock,它接受单个页面 URL 并返回其内容
  3. 第三个块可以是一个动作块,它接受页面并对其进行任何需要的操作。

例如:

var listBlock = new TransformManyBlock<Uri,Uri>(async uri=> 
{
    var content=await httpClient.GetStringAsync(uri);
    var uris=ProcessThePage(contents);
    return uris;
});


var downloadBlock = new TransformBlock<Uri,(Uri,string)>(async uri=> 
{
    var content=await httpClient.GetStringAsync(uri);
    return (uri,content);
});

var processingBlock = new ActionBlock<(Uri uri,string content)>(async msg=> 
{
    //Do something
    var pathFromUri(msg.uri);
    File.WriteAllText(pathFromUri,msg.content);
});

var linkOptions=new DataflowLinkOptions{PropagateCompletion=true};

listBlock.LinkTo(downloadBlock,linkOptions);    
downloadBlock.LinkTo(processingBlock,linkOptions);

每个块都使用自己的任务运行。您可以指定一个块可以使用多个任务,例如同时下载多个页面。

每个块都有一个输入和输出缓冲区。您可以指定输入缓冲区的限制,以避免使用太多消息来处理块。如果一个块达到限制,上游块将暂停。这样,您可以防止 downloadBlock 用数千页淹没缓慢的 processingBlock

一旦有了管道,您就可以将消息发布到第一个块。完成后,您可以将块告诉 Complete()。管道中的每个块将完成其输入缓冲区中的消息处理并将完成调用传播到下一个链接块。

您可以通过等待最后一个块的Completion 任务来等待所有消息完成。

var directoryPages=new Uri[]{..};

foreach(var uri in directoryPages)
{
    listBlock.Post(uri);
}

listBlock.Complete();

await processingBlock.Complete();

ExecutionDataflowBlockOptions 可用于指定使用多个任务和输入缓冲区限制,例如:

var options=new ExecutionDataflowBlockOptions 
            {
                BoundedCapacity=10, 
                MaxDegreeOfParallelism=4,
            };

var downloadBlock = new TransformBlock<Uri,(Uri,string)>(...,options);

这意味着downloadBlock 在向listBlock 发出暂停信号之前将接受最多 10 个 URI。它将同时处理多达 4 个 Uris

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-19
    • 1970-01-01
    • 2015-10-04
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 2016-07-26
    • 2011-10-16
    相关资源
    最近更新 更多