【问题标题】:Thread-pool friendly approach to Sleep?线程池友好的睡眠方法?
【发布时间】:2016-07-29 15:23:42
【问题描述】:

我想在 ASP.net 应用程序中插入睡眠(又名油门、延迟、tarpit、停留)(想象一下登录尝试失败、升级延迟之类的事情)。

protected void Page_Load(object sender, EventArgs e)
{
    Int32 sleepyTime = GetSleepyTime(Request);

    if (sleepyTime > 0)
        System.Threading.Thread.Sleep(sleepyTime);


    //...Continue normal processing
}

我希望所有剩余的处理继续正常进行;我只想让用户代理受苦。

问题在于 ASP.net 使用 ThreadPool 来处理请求。如果我要Sleep 5、10、30 秒,我会吃掉宝贵的有限资源。

我认为它需要类似于:

protected void Page_Load(object sender, EventArgs e)
{
    Int32 sleepyTime = GetSleepyTime(Request);

    if (sleepyTime > 0)
       ABetterKindOfSleep(sleepyTime);

    //...Continue normal processing
}

private void ABetterKindOfSleep(int milliseconds)
{
   await SleepAsync(milliseconds);
}

private async void SleepAsync(int milliseconds)
{
   System.Threading.Thread.Sleep(milliseconds);
}

但是从来没有写过任何 async/await 代码,也没有理解 asyncawait 去哪里的逻辑,或者为什么,或者即使它可以用来运行异步代码:我不知道是否可以用来运行异步代码。

阅读奖励

【问题讨论】:

  • 您的标签表明您使用的是 .NET 2.0。 async/await 模式是随 .NET 4.0 一起出现的。您选择了错误的标签还是故意的?
  • @Nitram 适用于旧网站的解决方案(例如,如果我在 2005 年问过这个问题)显然更好。特别是因为我有一些代码被锁定在 2.0 中。但如果 .NET 2 不支持线程池,我将不得不接受没有办法限制。但我会尽我所能。

标签: asp.net .net iis httpmodule


【解决方案1】:

async 等效于 Thread.Sleepawait Task.Delay

if (sleepyTime > 0)
  await Task.Delay(sleepyTime);

请注意,这必须在 async 方法的上下文中使用,并且对于可以使用 async 的位置存在限制(尤其是在 WebForms 上)。如需更多信息,请参阅我的article on async ASP.NETofficial tutorial on async WebForms

【讨论】:

    【解决方案2】:

    这很容易。

    首先你创建一个IHttpModule 类:

    class TarpitHttpModule : IHttpModule
    {
    }
    

    然后您通过在web.config 中注册来让 IIS 知道该模块:

    <configuration>
       <system.webServer>
          <modules runAllManagedModulesForAllRequests="true">
             <add name="Tarpit" type="TarpitHttpModule"/>
    

    如果你是卡西尼,添加到:

    <configuration>
       <system.web>
          <httpModules>
             <add name="Tarpit" type="TarpitHttpModule"/>
    

    只要有一个 http 请求进来,IIS 就会调用你的.Init 方法。这是您将使用以下方法注册异步事件处理程序的地方:

    代码:

    public void Init(HttpApplication application)
    {
        //This is the synchronous event handler; which we don't want
        //application.BeginRequest += new EventHandler(this.Application_BeginRequest);
    
        //EventHandlerTaskAsyncHelper requires .NET 4.5
        //https://brockallen.com/2013/07/27/implementing-async-http-modules-in-asp-net-using-tpls-task-api/ 
        //  Archive: http://archive.is/Cdvle
        //
        //Normally you'd have to write a pair of methods:
        //    application.AddOnBeginRequestAsync(OnBegin, OnEnd);
        //
        //and then we'd have to write an OnBegin which returns IAsyncResult, and then OnEnd which takes the IAsyncResult.
        //The modern way is to use Tasks, and use the IAsyncResult that a Task **is**.
        //Fortunately the .NET team wrote a handy class that wraps up the boilerplate catching faults, etc,
        //and created the EventHandlerTaskAsyncHelper class
    
        var beginTaskHelper = new EventHandlerTaskAsyncHelper(BeginRequestAsync);
        application.AddOnBeginRequestAsync(beginTaskHelper.BeginEventHandler, beginTaskHelper.EndEventHandler);
    }
    

    所以现在我们必须提供 BeginRequestAsync 异步处理程序:

    async Task BeginRequestAsync(object sender, EventArgs e)
    {
        var application = (HttpApplication)sender;
        var context = application.Context;
    
        // In reality i would use the context.Request to come up with a unique key 
        // for this user agent e.g. 
        String key = SHA256(UserHostAddress+UserAgent+AcceptTypes+UserLanguages).ToBase64();
        // And use that as a cache key store information about this user agent
        Object tarpitInfo = context.Cache.Get(agentIdentity);
        if (ti == null)
            return;
    
        // But in this SO demo, i'm just going to unconditionally sleep
        Boolean waitPerformed = await PerformDelay(context, tarpitInfo);
        if (waitPerformed)
        {
            context.Response.StatusCode = 429;
            context.Response.StatusDescription = "Too Many Requests";
            context.Response.End();
            return;
        }
    }
    

    然后是睡觉的工作:

    async Task<Boolean> PerformDelay(HttpContext context, TarInfo ti)
    {
        int delayMs = 3000;
        Task delay = Task.Delay(delayMs);
        await delay;
        return true;
    }
    

    【讨论】:

    • 等待大量请求的开销是什么?它没有阻塞线程,但它必须使用一些内存。难道它不能很容易地达到默认的最大并发请求限制吗?我认为是 5000 个请求。
    【解决方案3】:

    我还想对攻击登录端点的 bot 流量进行缓送,我担心如果我只是等待,我会达到最大并发请求或内存不足。我还没有找到一种使用 Windows 和 Asp.Net 的低开销方式。

    我喜欢done here 的方式,它改变了 TCP/IP 堆栈的行为以缩小窗口大小而不是 ACK 后续数据包,这使得远程以指数方式回退并且只发送少量数据。

    我可能会在前面添加一些运行 HAProxy 的 Linux VM,以利用其 DDOS capabilities

    【讨论】:

      猜你喜欢
      • 2014-06-07
      • 2014-05-15
      • 2018-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多