gpt4 book ai didi

c# - 异步任务的许多实例可以共享对并发集合的引用,并在C#中向其中并发添加项目吗?

转载 作者:行者123 更新时间:2023-12-03 13:20:30 24 4
gpt4 key购买 nike

我才刚刚开始学习C#线程和并发集合,并且不确定提出问题的合适术语,因此我将简要介绍我想做的事情。在这一点上,我对此主题的掌握最多是基本的。按照我的设想,下面的方法是否还可行?

  • 我必须在并发集合中有100,000个URL,必须对其进行测试-链接仍然有效吗?我有另一个并发集合,最初是空的,它将包含异步请求确定已移动的URL子集(400、404等错误)。
  • 我想在PC和我们的带宽允许的范围内并发产生尽可能多的异步请求,并以每秒20个async-web-request-tasks的速度开始工作,然后从那里开始。

  • 如果一个异步任务同时处理了这两个事情,是否可以工作:它将发出异步请求,然后在遇到4xx错误时将URL添加到BadUrls集合中?该任务的新实例每50毫秒产生一次:
         class TestArgs args {
    ConcurrentBag<UrlInfo> myCollection { get; set; }
    System.Uri currentUrl { get; set; }
    }

    ConcurrentQueue<UrlInfo> Urls = new ConncurrentQueue<UrlInfo>();
    // populate the Urls queue
    <snip>

    // initialize the bad urls collection
    ConcurrentBag<UrlInfo> BadUrls = new ConcurrentBag<UrlInfo>();


    // timer fires every 50ms, whereupon a new args object is created
    // and the timer callback spawns a new task; an autoEvent would
    // reset the timer and dispose of it when the queue was empty


    void SpawnNewUrlTask(){
    // if queue is empty then reset the timer
    // otherwise:
    TestArgs args = {
    myCollection = BadUrls,
    currentUrl = getNextUrl() // take an item from the queue
    };
    Task.Factory.StartNew( asyncWebRequestAndConcurrentCollectionUpdater, args);
    }



    public async Task asyncWebRequestAndConcurrentCollectionUpdater(TestArgs args)
    {
    //make the async web request
    // add the url to the bad collection if appropriate.
    }

    可行的?出路吗?

    最佳答案

    这种方法看起来不错,但是您显示的特定代码存在一些问题。

    但是在此之前,评论中已经提出了建议,即“任务并行性”是可行的方法。我认为这是误导的。有一个常见的误解,即如果您想并行进行大量工作,则必然需要大量线程。仅当工作受计算限制时,这才是正确的。但是您正在做的工作将受到IO的约束-此代码将花费其大部分时间等待响应。它将执行很少的计算。因此,在实践中,即使仅使用一个线程,您最初的每秒20个请求的目标似乎也不会导致单个CPU内核大汗淋漓的工作量。

    简而言之,单个线程可以处理非常高级别的并发IO。如果您需要并行执行代码,则只需要多个线程,而在这里似乎不太可能,因为在此特定工作中CPU的工作量很少。

    (这种误解早于awaitasync数年。实际上,它早于TPL-请参见http://www.interact-sw.co.uk/iangblog/2004/09/23/threadless以了解.NET 1.1时代的插图,该插图说明了如何使用少量线程处理成千上万个并发请求。由于这些基本原理至今仍然适用,因为Windows网络IO基本上仍以相同的方式工作。)

    并不是说在这里使用多个线程有什么特别的错误,我只是指出这有点让人分心。

    无论如何,回到您的代码。这行是有问题的:

    Task.Factory.StartNew( asyncWebRequestAndConcurrentCollectionUpdater, args);

    尽管您没有提供所有代码,但是我看不到如何编译。接受两个参数的 StartNew重载要求第一个参数是 ActionAction<object>Func<TResult>Func<object,TResult>。换句话说,它必须是不带任何参数或接受 object类型的单个参数(并且可能会或可能不会返回值)的方法。您的'asyncWebRequestAndConcurrentCollectionUpdater'采用 TestArgs类型的参数。

    但是,它不是可编译的事实并不是主要问题。这很容易解决。 (例如,将其更改为 Task.Factory.StartNew(() => asyncWebRequestAndConcurrentCollectionUpdater(args));)真正的问题是您正在做的事情有点奇怪:您正在使用 Task.StartNew调用已返回 Task的方法。
    Task.StartNew是一种采用同步方法(即不返回 Task的方法)并以非阻塞方式运行它的便捷方法。 (它将在线程池上运行。)但是,如果您已经有一个已经返回 Task的方法,那么您实际上并不需要使用 Task.StartNew。如果我们看一下 Task.StartNew返回的结果(一旦您解决了编译错误),就会变得更加奇怪:
    Task<Task> t = Task.Factory.StartNew(
    () => asyncWebRequestAndConcurrentCollectionUpdater(args));

    Task<Task>揭示了正在发生的事情。您已经决定使用一种通常用于使非异步方法异步的机制来包装已经异步的方法。因此,您现在有了一个产生 TaskTask

    其中一个令人有些意外的结果是,如果您要等待 StartNew返回的任务完成,则不一定要完成基础工作:
    t.Wait(); // doesn't wait for asyncWebRequestAndConcurrentCollectionUpdater to finish!

    实际要做的就是等待 asyncWebRequestAndConcurrentCollectionUpdater返回 Task。并且由于 asyncWebRequestAndConcurrentCollectionUpdater已经是一个异步方法,它将立即或多或少地返回一个任务。 (具体来说,它将在执行不立即完成的 await时返回任务。)

    如果您要等待已开始的工作完成,则需要执行以下操作:
    t.Result.Wait();

    或者,可能更有效地:
    t.Unwrap().Wait();

    那就是说:给我获取我的异步方法返回的 Task,然后等待。这可能与这个简单得多的代码没有什么有用的区别:
    Task t = asyncWebRequestAndConcurrentCollectionUpdater("foo");
    ... maybe queue up some other tasks ...
    t.Wait();

    通过引入`Task.Factory.StartNew',您可能没有获得任何有用的信息。

    我说“可以”是因为有一个重要的限定条件:它取决于您开始工作的环境。 C#生成的代码默认情况下会尝试确保在 async之后继续 await方法时,它在与最初执行 await相同的上下文中这样做。例如,如果您在WPF应用中,并且在UI线程上使用 await,则当代码继续执行时,它将在UI线程上进行操作。 (您可以使用 ConfigureAwait禁用此功能。)

    因此,如果您处于上下文本质上已被序列化的情况下(要么是因为它是单线程的(如GUI应用程序中的情况),要么是因为它使用类似于租用模型的内容,例如特定ASP的上下文.NET请求),实际上可以通过 Task.Factory.StartNew启动异步任务可能很有用,因为它使您可以转义原始上下文。但是,您的工作变得更加艰难-跟踪任务以完成任务会更加复杂。通过在 ConfigureAwait方法中使用 async,您可能已经能够实现相同的效果。

    无论如何,这可能都没关系-如果您仅尝试每秒处理20个请求,则所需的最少CPU工作量意味着您可以在一个线程上完全适本地管理它。 (此外,如果这是一个控制台应用程序,则使用线程池的默认上下文将起作用,因此您的任务在任何情况下都将能够运行多线程。)

    但是回到您的问题,对于我来说,只有一个 async方法从队列中挑选一个URL,发出请求,检查响应,并在必要时将一个条目添加到错误的url集合中,这似乎是完全合理的。从计时器开始的事情似乎也很合理-这将限制尝试连接的速率,而不会因响应缓慢而陷入困境(例如,如果大量请求最终试图与离线服务器通信)。如果遇到某种病理情况,最终导致连续出现成千上万个URL都指向无响应的服务器,则可能有必要为运行中的最大请求数设置上限。 (在相关说明中,您需要确保使用的HTTP API不会达到每个客户端连接的限制-最终可能会限制有效的吞吐量。)

    您将需要添加某种完成处理-仅启动异步操作并且不执行任何操作来处理结果是不好的做法,因为您最终可能会遇到无处可去的异常。 (在.NET 4.0中,这些命令通常用于终止您的进程,但从.NET 4.5开始,默认情况下,异步操作中未处理的异常将被忽略!)如果最终决定值得通过 Task.Factory.StartNew启动,请记住:最后,您需要进行额外的包装,因此您需要执行 myTask.Unwrap().ContinueWith(...)之类的操作才能正确处理它。

    关于c# - 异步任务的许多实例可以共享对并发集合的引用,并在C#中向其中并发添加项目吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18059904/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com