AVK Selected

Показавшиеся интересными, на мой вкус, посты

Синхронный и асинхронный IO

scale_tone scale_tone
Здравствуйте, Аноним, Вы писали:

А>Мне кажется второй способ более простой и удобнее для чтения отладки и т.п.

А>Есть ли какая-то эффективность у 1го способа или это уже можно считать как устаревший подход ?

На нынешнем этапе развития науки и техники устаревшим считается скорее второй подход.

Во всех трех вариантах:

1) Begin/End-методы,
2) обертка Task.FromAsync над ними, упомянутая TK
и
3) синхронные сетевые вызовы в теле таски,

непосредственно вызовы и ожидания ответов действительно происходят в потоках из IO-пула. Но вариант №3 потребляет _еще_один_ поток, в дополнение к уже потребляемым IO-потокам. Т.е. в общем случае требует в два раза больше потоков (=> в два раза больше памяти) и рано или поздно упрется в лимит на их число.

Преимущества первых двух вариантов над третьим особенно хорошо видны, если принудительно ограничить размер ThreadPool-а:

class Program
{
    const int CallCount = 30;
    // this service method sleeps for 1 second and returns some JSON
    const string LongMethodUri = "http://beginendtasktest.azurewebsites.net/api/values";

    static void Main()
    {
        // limiting the thread pool size
        ThreadPool.SetMinThreads(3, 3);
        ThreadPool.SetMaxThreads(3, 3);

        Measure(BeginEndTest);
        Measure(TaskFromAsyncTest);
        Measure(SynchronousTaskTest);

        int workerThreads, ioThreads;
        ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
        Console.WriteLine("Worker threads: {0}, IO threads: {1}", workerThreads, ioThreads);
        Console.WriteLine("Any key press you...");
        Console.ReadKey();
    }

    static void Measure(Action todo)
    {
        var sw = new Stopwatch();
        sw.Start();
        todo();
        sw.Stop();
        Console.WriteLine("{0} took {1} ms", todo.Method.Name, sw.ElapsedMilliseconds);
    }

    static void BeginEndTest()
    {
        var asyncResults = new Dictionary<WebRequest, IAsyncResult>();
        for (int i = 0; i < CallCount; i++)
        {
            var request = WebRequest.Create(new Uri(LongMethodUri));
            asyncResults[request] = request.BeginGetResponse(null, null);
        }
        foreach (var pair in asyncResults)
        {
            pair.Key.EndGetResponse(pair.Value);
        }
    }

    static void TaskFromAsyncTest()
    {
        var tasks = Enumerable.Range(0, CallCount).Select(i =>
        {
            var request = WebRequest.Create(new Uri(LongMethodUri));
            return Task.Factory.FromAsync
                (
                    request.BeginGetResponse,
                    (Func<IAsyncResult, WebResponse>)request.EndGetResponse,
                    null
                );

        });

        Task.WaitAll(tasks.ToArray());
    }

    static void SynchronousTaskTest()
    {
        var tasks = Enumerable.Range(0, CallCount).Select(i => Task.Run(() =>
        {
            using ((WebRequest.Create(new Uri(LongMethodUri))).GetResponse()) { }
        }));

        Task.WaitAll(tasks.ToArray());
    }
}



Важно не забыть убрать ограничение на число параллельных коннектов к одному и тому же адресу в app.config:
  <system.net>
    <connectionManagement>
      <remove address="*"/>
      <add address="*" maxconnection="999999"/>
    </connectionManagement>
  </system.net>



Имеем закономерный результат:
BeginEndTest took 2346 ms
TaskFromAsyncTest took 2245 ms
SynchronousTaskTest took 11067 ms
Worker threads: 3, IO threads: 3
Any key press you...



Однако! При определенных условиях синхронный вызов метода сервиса в таске может оказаться выгоднее по скорости. Например, если этот метод — очень быстрый.
Поднимем сервис на локальной машине, уберем из его метода Thread.Sleep() и снимем лимит на число потоков. Имеем:
BeginEndTest took 471 ms
TaskFromAsyncTest took 626 ms
SynchronousTaskTest took 202 ms
Worker threads: 32767, IO threads: 1000
Any key press you...



Таким образом, выяснять, какой из вариантов быстрее и эффективнее в Вашем конкретном случае, все равно придется с помощью нагрузочных тестов.

P.S. — речь здесь идет об огромном числе параллельных вызовов в нагруженных системах. Если у Вас виндовый клиент, дергающий что-то раз в час — все, что выше, можно не читать.
gravatar
Аноним BeginAsync vs Tasks
16.02.2014 03:04
_>Имеем закономерный результат:
_>
_>BeginEndTest took 2346 ms
_>TaskFromAsyncTest took 2245 ms
_>SynchronousTaskTest took 11067 ms
_>Worker threads: 3, IO threads: 3
_>Any key press you...
_>



_>Однако! При определенных условиях синхронный вызов метода сервиса в таске может оказаться выгоднее по скорости. Например, если этот метод — очень быстрый.

_>Поднимем сервис на локальной машине, уберем из его метода Thread.Sleep() и снимем лимит на число потоков. Имеем:
_>
_>BeginEndTest took 471 ms
_>TaskFromAsyncTest took 626 ms
_>SynchronousTaskTest took 202 ms
_>Worker threads: 32767, IO threads: 1000
_>Any key press you...
_>



Мне кажется тест не совсем корректный
т.к. в случае 3. синхронный метод скачивает все данные, в асинхронных вариантах первый callback ни о чем не говорит, там может быть принят только 1 байт.
Обычно в обработчиках запускается повторно BeginReceive пока не будут получены все данные, вот этого в тестах нет, поэтому вероятно тест с удаленным сервером дал такой более быстрый результат.
scale_tone
scale_tone
16.02.2014 04:16
Здравствуйте, Аноним, Вы писали:

А>Мне кажется тест не совсем корректный

А>т.к. в случае 3. синхронный метод скачивает все данные, в асинхронных вариантах первый callback ни о чем не говорит, там может быть принят только 1 байт.
А>Обычно в обработчиках запускается повторно BeginReceive пока не будут получены все данные, вот этого в тестах нет, поэтому вероятно тест с удаленным сервером дал такой более быстрый результат.

Код вызываемого метода:

// GET api/values
public IEnumerable<string> Get()
{
   Thread.Sleep(1000);
   return new string[] { "value1", "value2" };
}


Метод даже 1 байт выдает не сразу, а через секунду.

Впрочем, вставьте вычитывание всего респонса сами и проверьте.
gravatar
Аноним
16.02.2014 06:31
Здравствуйте, scale_tone, Вы писали:

_>Здравствуйте, Аноним, Вы писали:


А>>Мне кажется тест не совсем корректный

А>>т.к. в случае 3. синхронный метод скачивает все данные, в асинхронных вариантах первый callback ни о чем не говорит, там может быть принят только 1 байт.
А>>Обычно в обработчиках запускается повторно BeginReceive пока не будут получены все данные, вот этого в тестах нет, поэтому вероятно тест с удаленным сервером дал такой более быстрый результат.

_>Код вызываемого метода:


_>
_>// GET api/values
_>public IEnumerable<string> Get()
_>{
_>   Thread.Sleep(1000);
_>   return new string[] { "value1", "value2" };
_>}
_>


_>Метод даже 1 байт выдает не сразу, а через секунду.


_>Впрочем, вставьте вычитывание всего респонса сами и проверьте.


Ну так это ваш локальный, на нем собственно разницы и не видно, точнее синхронный даже в плюсе.
А на удаленном сервере такой же код ?
scale_tone
scale_tone
16.02.2014 07:29
Здравствуйте, Аноним, Вы писали:

А>Ну так это ваш локальный, на нем собственно разницы и не видно, точнее синхронный даже в плюсе.

А>А на удаленном сервере такой же код ?

Еще раз.

По адресу http://beginendtasktest.azurewebsites.net/api/values сейчас доступен метод, код которого следующий:
// GET api/values
public IEnumerable<string> Get()
{
   Thread.Sleep(1000);
   return new string[] { "value1", "value2" };
}


Этот код задеплоил туда я. Специально чтобы смоделировать долгий сетевой вызов.
То, что метод выполняется секунду, хорошо видно:
http://files.rsdn.org/94666/beginendtest.png

Множество параллельных синхронных вызовов этого метода в теле таски работает медленнее и хуже, чем такое же множество параллельных асинхронных вызовов.

Множество параллельных синхронных вызовов другого, более быстрого метода в теле таски иногда может работать быстрее, чем такое же множество параллельных асинхронных вызовов.