Эффективное использование Tpl.Dataflow

364
23 декабря 2016, 11:17

Задача такая: по приходу трека (их может быть тысячи) надо запрашивать данные из скробблера, обрабатывать ответ (в примере не показано ради наглядности), и записывать в файл. Решил использовать Tpl.Dataflow и вот что получилось:

static void Main()
{
    HttpClient hc = new HttpClient();
    StreamWriter sw = new StreamWriter(@"C:\res.txt");
    // первый вариант
    TransformBlock<string, string> tb = new TransformBlock<string, string>(item => hc.GetStringAsync(item), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4, BoundedCapacity = 200 });
    //второй вариант
    //TransformBlock<string, string> tb = new TransformBlock<string, string>(item => new HttpClient().GetStringAsync(item), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4, BoundedCapacity = 200 });
    ActionBlock<string> ab = new ActionBlock<string>(item =>
    {
        sw.WriteLine(item);
        sw.WriteLine("________________________________________________");
    }, new ExecutionDataflowBlockOptions { BoundedCapacity = 200 });
    tb.LinkTo(ab);
    tb.Completion.ContinueWith(item => ab.Complete());
    ab.Completion.ContinueWith(item => sw.Dispose());
    Stopwatch swa = new Stopwatch();
    swa.Start();
    foreach (var item in urls)
    {
        tb.Post(item);
    }
    tb.Complete();
    ab.Completion.Wait();
    Console.WriteLine(swa.ElapsedMilliseconds);
}

Как вы уже наверняка заметили, есть две ветки решения:

  1. В первом варианте я использую один объект HttpClient, но он одновременно может послать только два запроса (кстати, почему так?), из-за чего весь процесс происходит довольно долго

  2. Второй вариант уже быстрее, и значительно быстрее, если распараллеливать не на 4, а на 20, например, но и тут не без минусов: создание каждого объекта HttpClient влечет за собой установку соединения (рукопожатие), что логично. Но как я понимаю, это лишние накладные расходы, которых можно избежать. Из чего у меня назревает вопрос:

можно ли как-то привязать один объект HttpClient к таску, чтоб когда он выполнил один запрос, второй запрос происходил через тот же объект HttpClient и не происходила установка соединения. То есть я хочу, чтоб объектов HttpClient было столько же, сколько MaxDegreeOfParallelism и каждый таск использовал объект HttpClient, не занятый другими тасками в данный момент. Ну или другое эффективное решение

Заранее спасибо

Answer 1

Создайте пул объектов HttpClient. Перед запросом извлекайте объект из пула (или создавайте новый), а после запроса возвращайте в пул.

ConcurrentBag<HttpClient> pool = new ConcurrentBag<HttpClient>();
TransformBlock<string, string> tb = new TransformBlock<string, string>(async item => {
    HttpClient hc;
    if(!pool.TryTake(out hc)) {
        hc=new HttpClient();
    }
    try {
        return await hc.GetStringAsync(item).ConfigureAwait(false);
    } finally {
        pool.Add(hc);
    }
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4, BoundedCapacity = 200 });
Answer 2

Создайте вместо одного TransformBlock четыре. И каждому дайте свой HttpClient.

В качестве источника используйте еще один блок.

READ ALSO
c# парсинг времени [требует правки]

c# парсинг времени [требует правки]

Добрый день! У меня есть строка такого вида

325
inline термин в контексте C# / JIT компилятора

inline термин в контексте C# / JIT компилятора

Добрый деньСтолкнулся с таким вопросом,а именно хочу четко понять определение термина,такого как inline метод, соответственно в контексте C#(чтобы...

370
Обобщенный тип и неявное\явное указание типа

Обобщенный тип и неявное\явное указание типа

К примеру имеется два метода, которые выглядят достаточно тривиально,но возникает вопрос,можно ли назвать их следующим образом:

297