c# - 如何创建一个立即启动的线程(即使有很多线程就绪)?

标签 c# multithreading async-await clr

我正在尝试构建一个负载测试器,方法是创建一个发出快速异步 http 请求的线程,忽略服务器响应,另一个线程通过发送请求和测量响应时间每 500 毫秒对服务器进行一次采样。伪代码:

for (int i = 0; i < input.Length; i++)
{
    Console.WriteLine("Load: {0} requests/second", input[i]);
    var trd = LunchSamlper();
    var callsPerSecond = input[i];
    for (j = 0; j < callsPerSecond * BATCH_TIME; j++)
    {
        tasks.Add(Task.Factory.StartNew(async () =>
        {
            await SendRequestAsyncIgnoreResponse(address, payload);
        }));
        Thread.Sleep(1000 / callsPerSecond);
    }
    Task.WaitAll(tasks.ToArray());
    tasks.Clear();
    trd.Abort();
}

private static async Task SendRequestAsyncIgnoreResponse(Uri url, string data)
{
    WebClient client = new WebClient();
    client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
    await Task.Factory.StartNew(() => client.UploadStringAsync(url, data));
}

private static Thread LunchSamlper()
{
    var t = new Thread(() =>
    {
        Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
        Stopwatch sw = new Stopwatch();
        while (true)
        {
            sw.Restart();
            SendRequestAndProcessResponse();
            sw.Stop();
            Console.WriteLine("Time:{0}, Threads in process: {1}, Response Time: {2}", DateTime.Now.TimeOfDay, Process.GetCurrentProcess().Threads.Count, sw.ElapsedMilliseconds);
            Thread.Sleep(500);
        }
    });
    t.Priority = ThreadPriority.Highest;
    t.Start();
    return t;
}   

理想情况下,采样器线程每 500 毫秒唤醒一次,发出请求并测量响应时间。 实际上,线程每 10+ 秒唤醒一次,这使得测试无关紧要。我不太熟悉 CLR 内部结构,但我猜发生了什么事是异步 responses 快速排队,需要越来越多的线程来处理它们,从而插入采样器线程下调度程序。 我想做的(但不知道怎么做)是强制采样器线程保持在队列的顶部,有点欺骗微软声明他们使用的循环法。我尝试将其设置为“最高”优先级,但没有帮助。

编辑: 请看上面的新代码,这是我实际执行的代码,输出如下。您的所有评论都是正确的 - 采样器不受其余线程的影响(服务器响应太长)

但是,你可以清楚地看到负载越大,进程中呈现的线程就越多。我相信这强化了我的主张,即异步任务越多,线程池就需要更多的线程来处理它们当任务准备就绪时(即,当异步部分完成时)。当队列中准备好的任务太多时,线程池只能创建更多的线程。如果我错了,请向我解释幕后到底发生了什么。 输出:

Threads in process: 5

Load: 2 requests/second
Time :10:38:46.2552818, Threads in process: 19, Response     Time : 454
Time :10:38:47.1143283, Threads in process: 22, Response     Time : 357
Time :10:38:47.9765673, Threads in process: 22, Response     Time : 359
Time :10:38:48.8408045, Threads in process: 22, Response     Time : 360
.
. (same stats for 30 seconds)
.
Time :10:39:15.6071230, Threads in process: 23, Response     Time : 351
Load: 20 requests/second
Time :10:39:16.2120124, Threads in process: 23, Response     Time : 369
Time :10:39:17.0811746, Threads in process: 23, Response     Time : 365
Time :10:39:17.9329613, Threads in process: 24, Response     Time : 348
Time :10:39:18.7825313, Threads in process: 24, Response     Time : 346
Time :10:39:19.6345169, Threads in process: 24, Response     Time : 345
Time :10:39:20.4919706, Threads in process: 24, Response     Time : 354
.
.
.
.
Time :10:39:41.9171558, Threads in process: 26, Response     Time : 359
Time :10:39:42.7701623, Threads in process: 26, Response     Time : 351
Time :10:39:43.6266869, Threads in process: 26, Response     Time : 353
Time :10:39:44.4826907, Threads in process: 26, Response     Time : 352
Time :10:39:45.3433820, Threads in process: 26, Response     Time : 358
Load: 50 requests/second
Time :10:39:46.5745915, Threads in process: 26, Response     Time : 358
Time :10:39:47.9899573, Threads in process: 28, Response     Time : 912
Time :10:39:50.5339321, Threads in process: 30, Response     Time : 2039
Time :10:39:54.8185811, Threads in process: 28, Response     Time : 3780
Time :10:40:02.5677990, Threads in process: 28, Response     Time : 7244
Time :10:40:16.3460440, Threads in process: 30, Response     Time : 13273

边注:

起初,我没有使用采样线程。代码看起来像这样:

    for (int i = 0; i < numOfRequests; i++)
    {
        Stopwatch sw = new Stopwatch();
        Task.Factory.StartNew(async () => await SendRequestIgnoreResponse(sw))
            .ContinueWith(p=> MeasureServerResponse(sw));
        Thread.Sleep(1000 / requestsPerSecond);
    }

但我意识到测量的时间不正确,因为秒表还在计算响应准备好时任务在队列中花费的时间。这就是我想出采样器线程的想法的方式,它使它同步调用。任何新想法都会被认真考虑!

最后的注释:

值得一提的是,负载下的服务器是本地的,在生成负载的同一台机器上。寻址 url 背后的方法调用外部资源,这需要大约 300 毫秒。

最佳答案

您必须记住,当您的 IO 正确异步时,没有 spoon 线程。您创建新线程只是为了花费极少的时间开始一个异步操作,以及消耗线程池时间来处理您从未使用过的结果。

只是不要那样做:

for (int i = 0; i < numOfRequests; i++)
{
    SendRequestIgnoreResponse();
    Thread.Sleep(1000 / requestsPerSecond);
}

另请记住,Thread.Sleep 没有特别高的精度。它的精度最多可达几十毫秒,在压力下的系统中甚至可能达到数百毫秒。

关于c# - 如何创建一个立即启动的线程(即使有很多线程就绪)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29106253/

相关文章:

c# - 生成方法调用的代码。生成的 C# 代码显示的声明局部变量比 IL 代码中实际存在的变量多?

c# - 通用扩展方法测试

java - java中的http负载生成器

c# - 如何使用异步/等待方法管理类似 NDC 的 log4net 堆栈? (每个任务堆栈?)

c# - 如何将 WinForms TextBox 的前几个字符设置为只读?

c# - 绑定(bind)到用户控件的 DependencyProperty 不调用 setter

Xcode 7.3 : "Thread 1: EXC_BAD_ACCESS(code=1, adress=0x0)"

c# - BackgroundWorker 返回到错误的线程

jquery - MVC 4.5 异步 Ajax 调用不起作用

c# - 如何使用异步执行回调委托(delegate)