c#如何加载测试Web服务

时间:2017-02-08 13:13:07

标签: c# web-services parallel-processing task-parallel-library load-testing

我需要测试我们的应用程序和监视器中是否存在任何内存泄漏,以查看在处理请求时内存使用量是否增加太多。 我正在尝试开发一些代码来同时调用我们的api / webservice方法。这种api方法不是异步的,需要一些时间来完成其操作。

我对任务,线程和并行进行了大量研究,但到目前为止我没有运气。问题是,即使在尝试了以下所有解决方案之后,结果总是相同的,它似乎当时只处理两个请求。

尝试:

- >在一个简单的for循环中创建任务,并使用TaskCreationOptions.LongRunning

设置和创建任务

- >在简单的for循环中创建线程并使用和不使用高优先级启动它们

- >在简单的for循环上创建操作列表并使用

启动它们
Parallel.Foreach(list, options, item => item.Invoke)

- >直接在Parallel.For循环中运行(下面)

- >使用和不使用Options和TaskScheduler运行TPL方法

- >尝试使用MaxParallelism和最大线程的不同值

- >检查了this post,但它也没有帮助。 (我可以错过什么吗?)

- >在Stackoverflow中查看了一些其他帖子,但是使用F#解决方案,我不知道如何正确地将它们转换为C#。 (我从未使用过F#......)

(任务计划程序类取自msdn

这是我的基本结构:

public class Test
{
    Data _data;
    String _url;

    public Test(Data data, string url)
    {
        _data = data;
        _url = url;
    }

    public ReturnData Execute()
    {
         ReturnData returnData;

         using(var ws = new WebService())
         {
              ws.Url = _url;
              ws.Timeout = 600000;

              var wsReturn = ws.LongRunningMethod(data);

              // Basically convert wsReturn to my method return, with some logic if/else etc
         }
         return returnData;
    }
}

sealed class ThreadTaskScheduler : TaskScheduler, IDisposable
    {
        // The runtime decides how many tasks to create for the given set of iterations, loop options, and scheduler's max concurrency level.
        // Tasks will be queued in this collection
        private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();

        // Maintain an array of threads. (Feel free to bump up _n.)
        private readonly int _n = 100;
        private Thread[] _threads;

        public TwoThreadTaskScheduler()
        {
            _threads = new Thread[_n];

            // Create unstarted threads based on the same inline delegate
            for (int i = 0; i < _n; i++)
            {
                _threads[i] = new Thread(() =>
                {
                    // The following loop blocks until items become available in the blocking collection.
                    // Then one thread is unblocked to consume that item.
                    foreach (var task in _tasks.GetConsumingEnumerable())
                    {
                        TryExecuteTask(task);
                    }
                });

                // Start each thread
                _threads[i].IsBackground = true;
                _threads[i].Start();
            }
        }

        // This method is invoked by the runtime to schedule a task
        protected override void QueueTask(Task task)
        {
            _tasks.Add(task);
        }

        // The runtime will probe if a task can be executed in the current thread.
        // By returning false, we direct all tasks to be queued up.
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            return false;
        }

        public override int MaximumConcurrencyLevel { get { return _n; } }

        protected override IEnumerable<Task> GetScheduledTasks()
        {
            return _tasks.ToArray();
        }

        // Dispose is not thread-safe with other members.
        // It may only be used when no more tasks will be queued
        // to the scheduler.  This implementation will block
        // until all previously queued tasks have completed.
        public void Dispose()
        {
            if (_threads != null)
            {
                _tasks.CompleteAdding();

                for (int i = 0; i < _n; i++)
                {
                    _threads[i].Join();
                    _threads[i] = null;
                }
                _threads = null;
                _tasks.Dispose();
                _tasks = null;
            }
        }
   }

测试代码本身:

private void button2_Click(object sender, EventArgs e)
    {
        var maximum = 100;
        var options = new ParallelOptions
        {
             MaxDegreeOfParallelism = 100,
             TaskScheduler = new ThreadTaskScheduler()
        };

        // To prevent UI blocking
        Task.Factory.StartNew(() =>
        {
            Parallel.For(0, maximum, options, i =>
            {
                var data = new Data();
                // Fill data
                var test = new Test(data, _url); //_url is pre-defined
                var ret = test.Execute();

               // Check return and display on screen
               var now = DateTime.Now.ToString("HH:mm:ss");
               var newText = $"{Environment.NewLine}[{now}] - {ret.ReturnId}) {ret.ReturnDescription}";

               AppendTextBox(newText, ref resultTextBox);
           }
     }

    public void AppendTextBox(string value, ref TextBox textBox)
    {
        if (InvokeRequired)
        {
            this.Invoke(new ActionRef<string, TextBox>(AppendTextBox), value, textBox);
            return;
        }
        textBox.Text += value;
    }

我得到的结果基本上是这样的:

[10:08:56] - (0) OK
[10:08:56] - (0) OK
[10:09:23] - (0) OK
[10:09:23] - (0) OK
[10:09:49] - (0) OK
[10:09:50] - (0) OK
[10:10:15] - (0) OK
[10:10:16] - (0) OK
etc

据我所知,服务器端没有限制。我对并行/多任务世界相对较新。有没有其他方法可以做到这一点?我错过了什么吗?

(我简化了所有代码的清晰度,我相信所提供的代码足以描述上述场景。我也没有发布应用程序代码,但它只是一个简单的WinForms屏幕来调用如果任何代码有某种相关性,请告诉我,我也可以编辑和发布。)

提前致谢!

EDIT1:我在服务器日志上检查了它是否一两接收请求,所以它确实与发送它们有关,而不是接收。 它可能是与框架如何管理请求/连接有关的网络问题/限制吗?或者与网络有什么关系(与.net无关)?

EDIT2:忘记提及,它是一个SOAP Web服务。

EDIT3:我发送的一个属性(内部数据)需要针对每个请求进行更改。

EDIT4:我注意到,如果它们相关,那么每对请求之间的间隔总是约为25秒。

3 个答案:

答案 0 :(得分:2)

我建议不要重新发明轮子,只使用现有的解决方案之一:

  1. 最明显的选择:如果您的Visual Studio许可证允许您使用MS负载测试框架,那么您很可能甚至不必编写一行代码:How to: Create a Web Service Test
  2. SoapUI是一个免费的开源Web服务测试工具,它有一些限制load testing capabilities
  3. 如果由于某些原因SoapUI不合适(即您需要从多个主机以集群模式运行负载测试,或者您需要更多增强的报告),您可以使用Apache JMeter - 免费和开源的多协议负载测试工具也支持web services load testing

答案 1 :(得分:1)

在不编写自己的项目的情况下创建负载测试的好方法是使用此服务https://loader.io/targets

对于小型测试,它是免费的,你可以POST参数,标题,...并且你有一个很好的报告。

答案 2 :(得分:0)

默认的maxconnection = 2限制connectionManagement的结果是“一次两个请求”吗?

<configuration>  
  <system.net>  
    <connectionManagement>  
      <add address = "http://www.contoso.com" maxconnection = "4" />  
      <add address = "*" maxconnection = "2" />  
    </connectionManagement>  
  </system.net>  
</configuration>