简体   繁体   English

HttpClient.PostAsync挂起,直到启动所有System.Timers

[英]HttpClient.PostAsync Hanging Until All System.Timers Are Started

I'm having an odd problem with HttpClient and Timers. 我在使用HttpClient和Timers时遇到了一个奇怪的问题。 I have a large number of objects (up to 10,000) that post to a web service. 我有大量对象(最多10,000个)发布到Web服务。 These objects are on timers and post to the service at some n time after creation. 这些对象在计时器上,并在创建后的n个时间发布到服务。 The problem is that the Post stalls, until all of the timers have started. 问题在于Post停滞不前,直到所有计时器都已启动。 See the code below for an example. 有关示例,请参见下面的代码。

Q: Why does the Post hang until all Timers have started? 问:为什么在所有计时器启动之前,帖子会一直挂起? How can it be fixed so that the post functions correctly while the other Timers start? 如何解决该问题,以便在其他计时器启动时帖子能够正确运行?

public class MyObject
{
    public void Run()
    {
        var result = Post(someData).Result; 
        DoOtherStuff();
    }
}

static async Task<string> Post(string data)
{
    using (var client = new HttpClient())
    {
        //Hangs here until all timers are started
        var response = await client.PostAsync(new Uri(url), data).ConfigureAwait(continueOnCapturedContext: false);
        var text = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false);

        return text;
    }
}

static void Main(string[] args)
{
    for (int i = 0; i < 1000; i++)
    {
        TimeSpan delay = TimeSpan.FromSeconds(1);
        if (i % 2 == 0) delay = TimeSpan.FromDays(1);

        System.Timers.Timer timer = new System.Timers.Timer();
        timer.AutoReset = false;
        timer.Interval = delay.TotalMilliseconds;
        timer.Elapsed += (x, y) =>
            {
                MyObject o = new MyObject();
                o.Run();
            };

        timer.Start();
    }

    Console.ReadKey();
}

Because you're using up all the ThreadPool threads. 因为您用完了所有ThreadPool线程。

There's a lot wrong with your sample code. 您的示例代码有很多错误。 You're killing any chance of having reasonable performance, not to mention that the whole thing is inherently unstable. 您正在扼杀拥有合理性能的任何机会,更不用说整个过程本质上是不稳定的。

You're creating a thousand timers in a loop. 您正在循环创建一千个计时器。 You're not keeping a reference to any of them, so they will be collected the next time the GC runs - so I'd expect that in practice, very few of them will actually run, unless there's very little memory allocated until they actually get to run. 您没有保留对它们的任何引用,因此它们将在下一次GC运行时被收集-因此我希望在实践中,只有很少的内存会真正运行,除非在它们实际分配之前分配的内存很少开始运行。

The timer's Elapsed event will be invoked on a ThreadPool thread. 计时器的Elapsed事件将在ThreadPool线程上调用。 In that thread, you synchronously wait for a bunch of asynchronous calls to complete. 在该线程中,您同步等待一堆异步调用完成。 That means you're now wasting a thread pool thread, and completely wasting the underlying asynchronicity of the asynchronous method. 这意味着您现在正在浪费线程池线程,并且完全浪费了异步方法的底层异步性。

The continuation to the asynchronous I/O will be posted to ThreadPool as well - however, the ThreadPool is full of timer callbacks. 异步I / O的延续也将发布到ThreadPool-但是,ThreadPool充满了计时器回调。 It will slowly start creating more and more threads to accomodate the amount of work scheduled, until it finally is able to execute the first callback from the asynchronous I/O and it slowly untangles itself. 它会慢慢开始创建越来越多的线程来适应计划的工作量,直到最终它能够执行来自异步I / O的第一个回调,并逐渐使自身纠结。 At this point, you likely have more than 1000 threads, and are showing a complete misunderstanding of how to do asynchronous programming. 此时,您可能有1000多个线程,并且显示出对如何进行异步编程的完全误解。

One way (still rather bad) to fix both problems is to simply make use of asynchronicity all the time: 解决这两个问题的一种方法(仍然很糟糕)是始终仅使用异步性:

public class MyObject
{
    public async Task Run()
    {
        var result = await Post(someData);
        DoOtherStuff();
    }
}

static async Task<string> Post(string data)
{
    using (var client = new HttpClient())
    {
        //Hangs here until all timers are started
        var response = await client.PostAsync(new Uri(url), new StringContent(data)).ConfigureAwait(continueOnCapturedContext: false);
        var text = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false);

        return text;
    }
}

static void Main(string[] args)
{
    var tasks = new List<Task>();

    for (int i = 0; i < 1000; i++)
    {
        TimeSpan delay = TimeSpan.FromSeconds(1);
        if (i % 2 == 0) delay = TimeSpan.FromDays(1);

        tasks.Add(Task.Delay(delay).ContinueWith((_) => new MyObject().Run()));
    }

    Task.WaitAll(tasks.ToArray());

    Console.WriteLine("Work done");
    Console.ReadKey();
}

A much better way would be to implement some scheduler that handles dispatching the asynchronous I/O with the throttling you need. 更好的方法是实现一些调度程序,该调度程序处理需要分配的异步I / O。 You probably want to limit the number of concurrent requests or something like that, rather than running the requests in pre-defined intervals (and ignoring the fact that some requests might take very long, timeout or some such). 您可能希望限制并发请求的数量或类似的数量,而不是按预定义的时间间隔运行请求(并且忽略某些请求可能会花费很长时间,超时或类似的事实)。

As mentioned in another reply, that Result property is the problem. 如另一个答复中所述,该Result属性就是问题所在。 when you are using it, asyc will be come sync . 当您使用它时, asycsync If you want to run async operations in console or windows service applications, try Nito AsyncEx library. 如果要在控制台或Windows服务应用程序中运行异步操作,请尝试Nito AsyncEx库。 It creates an AsyncContext. 它创建一个AsyncContext。 Now you can change void Run to Task Run which is await-able and doesn't need the blocking Result property and in this case await Post will work in the Run method. 现在,您可以将void Run更改为Task Run ,它可以等待并且不需要阻塞Result属性,在这种情况下, await Post将在Run方法中工作。

static void Main(string[] args)
{
    AsyncContext.Run(async () =>
    {
            var data = await ...;
    });
}

It's because the timers are set up very quickly so they have all finished setting up by the time PostAsync completes. 这是因为计时器设置得非常快,所以在PostAsync完成时它们都已完成设置。 Try putting a Thread.Sleep(1000) after timer.Start , this will slow down the set up of your timers and you should see some PostAsync executions complete. 尝试将Thread.Sleep(1000) timer.Start之后,这会减慢计时器的设置,您应该会看到一些PostAsync执行已完成。

By the way, Task.Result is a blocking operation, which can cause a deadlock when run from a GUI application. 顺便说一句, Task.Result是一个阻塞操作,当从GUI应用程序运行时,它可能导致死锁。 There is more information in this article. 本文中有更多信息

As you're running on a console application, which uses the default ThreadPoolSynchronizationContext, you shouldn't really be experiencing the "hanging" feeling as if you are in a UI application. 当您在使用默认ThreadPoolSynchronizationContext的控制台应用程序上运行时,您实际上不应像在UI应用程序中那样感到“挂”的感觉。 I assume its because Post is taking longer to return than to allocate 1000 timers. 我认为这是因为Post返回花费的时间比分配1000个计时器要长。

In order for your method to run async , it has to go "async all the way . Using the Task.Result property, as mentioned before will simply block the asynchronous operation until it completes. 为了使您的方法运行async ,它必须一直“异步” 。使用Task.Result属性(如前所述)将简单地阻止异步操作,直到完成为止。

Lets see what we need to do for this to be "async all the way": 让我们看看我们要做到“一路异步”:

First, lets change Run from void to async Task so we can await on the Post method: 首先,将Runvoid更改为async Task以便我们可以await Post方法:

public async Task Run()
{
    var result = await Post(someData);
    DoOtherStuff();
}

Now, since Run became awaitable, as it returns a Task , we can turn Timer.Elapsed to an async event handler and await on Run . 现在,由于Run等待,因为它返回Task ,所以我们可以将Timer.Elapsed转到async事件处理程序,然后在Run await

static void Main(string[] args)
{
   for (int i = 0; i < 1000; i++)
   {
       TimeSpan delay = TimeSpan.FromSeconds(1);
       if (i % 2 == 0) delay = TimeSpan.FromDays(1);

       System.Timers.Timer timer = new System.Timers.Timer();
       timer.AutoReset = false;
       timer.Interval = delay.TotalMilliseconds;
       timer.Elapsed += async (x, y) =>
       {
            MyObject o = new MyObject();
            await o.Run();
       };

        timer.Start();
   }

   Console.ReadKey();
}

That's it, now we flow async all the way down to the HTTP request and back. 就是这样,现在我们一直异步传输到HTTP请求,然后再返回。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM