Artykuł

freeimages.com freeimages.com
cze 05 2015
0

HttpClient - równoległe pobieranie danych z różnych żródeł

Rynek aplikacji mobilnych rozwija się niezwykle dynamicznie w ostatnich latach. Programy na telefony dotykają dzisiaj naprawdę wielu ważnych i istotnych obszarów naszego codziennego życia. Coraz częściej tego rodzaju aplikacje funkcjonują w chmurze, dzięki czemu mamy łatwy dostęp do wszystkich niezbędnych nam informacji, niezależnie od tego gdzie jesteśmy i z jakiego urządzenia aktualnie korzystamy.

Wykorzystanie chmury wiąże się najczęściej z użyciem odpowiedniego API, do którego podłączają się wszystkie aplikacje klienckie. W wielu przypadkach programy mobilne muszą wysyłać kilka różnych żądań na raz. Oczywiście w .Net mamy odpowiednie klasy do obsługi komunikacji sieciowej. Jedną z takich konstrukcji jest klasa HttpClient umieszczona w przestrzeni System.Net.Http. HttpClient działa naprawdę bardzo sprawnie, aczkolwiek jest jeden mały problem z tą konstrukcją - trzeba napisać trochę dodatkowego kodu, który pozwoli na równoległe wykonywanie wielu różnych żądań. Na szczęście nie jest to trudne i postaram się tego dowieźć w niniejszym tekście.

Wprowadzenie

Klasa HttpClient z przestrzeni nazw System.Net.Http została wprowadzona w .Net Frameworku w wersji 4.5 i założenia ma być odpowiedzialna za obsługę komunikacji sieciowej. W praktyce mogę potwierdzić, że działa ona naprawdę bardzo sprawnie i dobrze nadaje się do obsługi wszelkiej maści RESTowych API. Główną zaletą tego rozwiązania jest obsługa różnych metod protokołu HTTP oraz asynchroniczność.

Klasa sama w sobie nie obsługuje wielu żądań jednocześnie, ale dzięki temu że wykorzystuje ona Taski, to bardzo łatwo jest tu wpiąć systemowe rozwiązania, które umożliwią tego typu operacje.

Przykład wykorzystany w niniejszym tekście, będzie rozszerzeniem artykułu z MSDN.

Przykład

W ramach przykładu będziemy chcieli pobrać równocześnie zawartość głównych stron kilku popularnych, polskich portali. W tym celu zbudowałem prostą aplikację konsolową, która będzie odpowiedzialna za realizację tego zadania.

Najważniejszym elementem naszego przykładu, będzie klasa Downloader oraz jedyna jej publiczna metoda Download:

class Downloader
{
    public async Task Download(IEnumerable<string> urls)
    {
        try
        {
            await DownloadPages(urls);
            Console.WriteLine("Zakończono pobieranie.");
        }
        catch (Exception)
        {
            Console.WriteLine("Nie udało się pobrać danych");
        }
    }
	
	// ...	
}

W praktyce metoda ta przyjmuje listę URLi do pobrania, zapewnia obsługę wyjątków i wywołuję lokalną, asynchroniczną metodę DownloadPages, która w istocie wykonuje najważniejszą robotę. Przyjrzymy się zatem bliżej kolejnej metodzie:

private async Task DownloadPages(IEnumerable<string> urls)
{
    HttpClient client = new HttpClient();

    IEnumerable<Task<Tuple<string, int>>> downloadTasksQuery =
        from url in urls select ProcessURL(url, client);

    List<Task<Tuple<string, int>>> downloadTasks = downloadTasksQuery.ToList();

    while (downloadTasks.Count > 0)
    {
        Task<Tuple<string, int>> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);

        Tuple<string, int> taskData = await finishedTask;
        Console.WriteLine("Zakończono pobieranie strony {0} o wielkości {1}.", 
            taskData.Item1, taskData.Item2);
    }
}

private async Task<Tuple<string, int>> ProcessURL(string url, HttpClient client)
{
    HttpResponseMessage response = await client.GetAsync(url);
    byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
    return Tuple.Create<string, int>(url, urlContents.Length);
}

Wewnątrz nowej metody, tworzymy obiekt klasy HttpClient, a następnie kolejkujemy zadania odpowiedzialne za pobranie poszczególnych stron. Kluczowym elementem listingu jest pętla while, która funkcjonuje dopóki na naszej liście zadań znajdują się jakieś niezakończone Taski.

Metoda Task.WhenAny oczekuje na zakończenie dowolnego zadania. Po jego zakończeniu, zadanie jest zdejmowane z listy, a jego wynik zostaje wyrzucony w oknie konsoli. W kolejnym kroku proces znowu staje na instrukcji Task.WhenAny (słówko kluczowe await) i tak w kółko, aż do wyczyszczenia listy.

Punktem wejściowym całej aplikacji jest oczywiście klasa Program, w której to tworzymy obiekt klasy Downloader i wykonujemy na nim metodę Download do której przekazujemy listę adresów.

class Program
{
    static void Main(string[] args)
    {
        Downloader downloader = new Downloader();
        Task.Run(async () =>
        {
            await downloader.Download(new List<string>{
            "http://www.onet.pl",
            "http://www.gazeta.pl",
            "http://www.interia.pl",
            "http://www.wp.pl",
            "http://www.wnp.pl",
        });
        }).Wait();
        Console.WriteLine("Naciśnij dowolny klawisz aby zakończyć...");
        Console.ReadKey();
    }
}

Przykładowy wynik działania aplikacji widoczny jest na screenie poniżej. Jak widać, kolejność adresów nie ma znaczenia - równoległe przetwarzanie naprawdę działa:)

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus