Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/334.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么多个短任务最终会得到相同的id?_C#_Async Await - Fatal编程技术网

C# 为什么多个短任务最终会得到相同的id?

C# 为什么多个短任务最终会得到相同的id?,c#,async-await,C#,Async Await,我有一个要处理的项目列表,并为每个项目创建一个任务,然后使用task.WhenAny()等待。我遵循此处描述的模式: 我改变了一件事:我正在使用HashSet而不是List。但我注意到所有任务最终都会得到相同的id,因此HashSet只添加了其中一个,因此我最终只等待一个任务 我在dotnetfiddle中有一个工作示例: 同时粘贴以下代码: using System; using System.Collections.Generic; using System.Threading.Tasks;

我有一个要处理的项目列表,并为每个项目创建一个任务,然后使用task.WhenAny()等待。我遵循此处描述的模式:

我改变了一件事:我正在使用
HashSet
而不是
List
。但我注意到所有任务最终都会得到相同的id,因此
HashSet
只添加了其中一个,因此我最终只等待一个任务

我在dotnetfiddle中有一个工作示例:

同时粘贴以下代码:

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

namespace ReproTasksWithSameId
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            List<int> itemIds = new List<int>() { 1, 2, 3, 4 };
            await ProcessManyItems(itemIds);
        }

        private static async Task ProcessManyItems(List<int> itemIds)
        {
            //
            // Create tasks for each item and then wait for them using Task.WhenAny
            // Following Task.WhenAny() pattern described here: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/start-multiple-async-tasks-and-process-them-as-they-complete
            // But replaced List<Task> with HashSet<Task>.
            //

            HashSet<Task> tasks = new HashSet<Task>();

            // We map the task ids to item ids so that we have enough info to log if a task throws an exception.
            Dictionary<int, int> taskIdToItemId = new Dictionary<int, int>();

            foreach (int itemId in itemIds)
            {
                Task task = ProcessOneItem(itemId);
                Console.WriteLine("Created task with id: {0}", task.Id);
                tasks.Add(task);
                taskIdToItemId[task.Id] = itemId;
            }

            // Add a loop to process the tasks one at a time until none remain.
            while (tasks.Count > 0)
            {
                // Identify the first task that completes.
                Task task = await Task.WhenAny(tasks);

                // Remove the selected task from the list so that we don't
                // process it more than once.
                tasks.Remove(task);

                // Get the item id from our map, so that we can log rich information.
                int itemId = taskIdToItemId[task.Id];

                try
                {
                    // Await the completed task.
                    await task;  // unwrap exceptions.
                    Console.WriteLine("Successfully processed task with id: {0}, itemId: {1}", task.Id, itemId);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Failed to process task with id: {0}, itemId: {1}. Just logging & eating the exception {1}", task.Id, itemId, ex);
                }
            }
        }
        private static async Task ProcessOneItem(int itemId)
        {
            // Assume this method awaits on some asynchronous IO.
            Console.WriteLine("item: {0}", itemId);
        }
    }
}
因此,基本上程序在等待第一个任务后退出

  • 为什么多个短任务最终会得到相同的id?顺便说一句,我还使用了一种返回
    Task
    而不是
    Task
    的方法进行了测试,在这种情况下效果很好

  • 我可以使用更好的方法吗?


  • 这是因为
    ProcessOneItem
    不是异步的

    您应该看到以下警告:

    此异步方法缺少“await”运算符,将同步运行。考虑使用“Acess”操作符等待非阻塞API调用,或“等待任务.Run(…)”在后台线程上执行CPU绑定的工作。
    wait(…)
    添加到
    ProcessOneItem
    后,返回任务将有一个id。

    问题的代码是同步的,因此只有一个已完成的任务
    async
    不会使某些东西异步运行,它是一种语法糖,允许使用
    await
    等待已经执行的异步操作完成,而不会阻塞调用线程

    至于文档示例,就是这样。一个文档示例,不是一个模式,当然也不是可以在生产中使用的东西,除了简单的案例

    如果一次只能发出5个请求以避免网络或CPU被淹没,会发生什么情况?你只需要下载固定数量的记录就可以了。如果您需要处理下载的数据,该怎么办?如果URL列表来自另一个线程怎么办

    这些问题由并发容器、发布/订阅模式以及专门构建的数据流和通道类来处理

    数据流

    较旧的数据流类负责缓冲输入和输出,并自动处理辅助任务。整个下载代码可以替换为:

    我们可以使用其他块(如TransformBlock)生成输出,将其传递给另一个块,从而创建并发处理管道。假设我们有两种方法,
    DownloadURL
    ParseResponse
    ,而不仅仅是
    ProcessUrl

    Task<string> DownloadUrlAsync(string url,HttpClient client)
    {
        return client.GetStringAsync(url);
    }
    
    void ParseResponse(string content)
    {
        var object=JObject.Parse();
        DoSomethingWith(object);
    }
    
    频道

    推出围棋式频道。这些实际上是数据流块的较低级别的概念。如果2012年频道可用,它们将使用频道编写

    等效的下载方法如下所示:

    ChannelReader<string> Downloader(ChannelReader<string> ulrs,HttpClient client,
                                     int capacity,CancellationToken token=default)
    {
        var channel=Channel.CreateBounded(capacity);
        var writer=channel.Writer;
    
        _ = Task.Run(async ()=>{
            await foreach(var url in urls.ReadAsStreamAsync(token))
            {
                var response=await client.GetStringAsync(url);
                await writer.WriteAsync(response);
            }
        }).ContinueWith(t=>writer.Complete(t.Exception));
        return channel.Reader;
    }
    
    出版商可以创建自己的频道,并将阅读器传递给
    下载程序
    方法。他们也不需要提前发布任何内容:

    var channel=Channel.CreateUnbounded<string>();
    var dlReader=Downloader(channel.Reader,client,5,5);
    foreach(var url in someUrlList)
    {
        await channel.Writer.WriteAsync(url);
    }
    channel.Writer.Complete();
    
    从财产文件中:

    任务ID是按需分配的,不一定表示创建任务实例的顺序。请注意,尽管冲突非常罕见,但任务标识符不能保证唯一


    据我所知,该属性主要用于调试目的。您可能应该避免在生产代码中依赖它。

    这不是一种模式,它只是一个演示任务的文档示例。除了非常简单的情况外,它并不适用于生产场景。有更好的类来处理发布/订阅、多个worker和任务,比如Dataflow的ActionBlock和System.Threading通道?此代码中没有活动任务。编译器应该已经发出警告,
    ProcessOneItem
    不包含
    wait
    ,因此将同步运行。这里没有任务,所有任务都在主线程上运行。如果同步运行代码,每次都返回相同的已完成任务。添加一个
    等待任务。延迟(100)
    在该方法中,它将返回新任务。
    有更好的方法吗?
    做什么?具体细节很重要。处理1000个URL需要不同于处理100K内存元素的体系结构。如果您对此有答案,最好将其标记为这样,并询问一个新问题。谢谢您提供的详细信息。正在阅读。@Turbo添加了另一个关于ChannelsDataflow的部分,这是IMHO执行此任务的正确工具。它是一个涵盖缓冲和处理的完整解决方案。通道非常适合缓冲(实际上可能是完美的异步队列),但在处理部分没有提供任何帮助。因此,您最终会手动生成任务,并编写具有问题异常处理特征的次优处理循环。例如,对于本例中的第二个
    下载程序
    ,单个异常将杀死一个辅助任务,从而降低并行度。在处理完所有URL或所有工作线程都已死亡之前,不会传播错误。
    Task<string> DownloadUrlAsync(string url,HttpClient client)
    {
        return client.GetStringAsync(url);
    }
    
    void ParseResponse(string content)
    {
        var object=JObject.Parse();
        DoSomethingWith(object);
    }
    
    var dlOptions=new ExecutionDataflowBlockOptions(){
        MaxDegreeOfParallelism=5,
        BoundedCapacity=5,
        CancellationToken=cts.Token
    };
    var downloader=new TransformBlock<string,string>(
                       url=>DownloadUrlAsync(url,client),
                       dlOptions);
    
    var parseOptions = new ExecutionDataflowBlockOptions(){
        MaxDegreeOfParallelism=10,
        BoundedCapacity=2,
        CancellationToken=cts.Token
    };
    var parser=new ActionBlock<string>(ParseResponse);
    
    downloader.LinkTo(parser, new DataflowLinkOptions{PropageateCompletion=true});
    
    foreach(var url in urls)
    {
        await downloader.SendAsync(url);
    }
    //Tell the block we're done
    downloader.Complete();
    //Wait until all urls are parsed
    await parser.Completion;
    
    ChannelReader<string> Downloader(ChannelReader<string> ulrs,HttpClient client,
                                     int capacity,CancellationToken token=default)
    {
        var channel=Channel.CreateBounded(capacity);
        var writer=channel.Writer;
    
        _ = Task.Run(async ()=>{
            await foreach(var url in urls.ReadAsStreamAsync(token))
            {
                var response=await client.GetStringAsync(url);
                await writer.WriteAsync(response);
            }
        }).ContinueWith(t=>writer.Complete(t.Exception));
        return channel.Reader;
    }
    
    ChannelReader<string> Downloader(ChannelReader<string> ulrs,HttpClient client,
                                     int capacity,int dop,CancellationToken token=default)
    {
        var channel=Channel.CreateBounded(capacity);
        var writer=channel.Writer;
    
        var tasks  = Enumerable
                       .Range(0,dop)
                       .Select(_=> Task.Run(async ()=>{
                           await foreach(var url in urls.ReadAllAsync(token))
                           {
                               var response=await client.GetStringAsync(url);
                               await writer.WriteAsync(response);
                           }
                        });
        _=Task.WhenAll(tasks)
              .ContinueWith(t=>writer.Complete(t.Exception));
        return channel.Reader;
    }
    
    var channel=Channel.CreateUnbounded<string>();
    var dlReader=Downloader(channel.Reader,client,5,5);
    foreach(var url in someUrlList)
    {
        await channel.Writer.WriteAsync(url);
    }
    channel.Writer.Complete();
    
    ChannelReader<T> Generate<T>(this IEnumerable<T> source)
    {
        var channel=Channel.CreateUnbounded<T>();
        foreach(var item in source)
        {
            channel.Writer.TryWrite(T);
        }
        channel.Writer.Complete();
        return channel.Reader;
    }
    
    var pipeline= someUrls.Generate() 
                          .Downloader(client,5,5);