如何创建一个立即启动的线程(即使有很多线程准备好了)?

时间:2015-03-17 17:50:31

标签: 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;
}   

理想情况下,采样器线程每500ms唤醒一次,发出请求并测量响应时间。 实际上,线程每10秒唤醒一次,使测试无关紧要。我对CLR内部结构的了解不太熟悉,但我猜测发生的事情是async 响应快速排队,需要越来越多的线程来处理它们,从而推动了采样器线程调度程序。 我想做的(但不知道如何)是强制采样器线程保持在队列的顶部,有点欺骗微软声称他们使用的循环。我试着给它“最高”优先权,但它没有帮助。

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

但是,您可以清楚地看到,负载越多,流程中呈现的线程就越多。我相信这强化了我的主张,即更多的异步任务,线程池需要更多的线程来处理它们当任务准备就绪时(也就是说,当异步部分完成时)。当队列中有太多任务准备就绪时,线程池别无选择,只能创建更多线程。如果我错了,请向我解释幕后的真实情况。 输出:

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毫秒。

1 个答案:

答案 0 :(得分:1)

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

请不要这样做:

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

另请注意,Thread.Sleep没有特别高的精确度。它的精度最低可达几十毫秒,在压力系统中甚至可能达到几百毫秒。