2011-07-16 9 views
7

Mam komponent, który przetwarza wiele żądań sieciowych w osobnym wątku. Każde przetwarzanie WebRequest jest synchroniczne.Lepsze podejście do zarządzania wieloma WebRequest

public class WebRequestProcessor:System.ComponentModel.Component 
{ 
    List<Worker> tlist = new List<Worker>(); 
    public void Start() 
    { 
     foreach(string url in urlList){ 
      // Create the thread object. This does not start the thread. 
      Worker workerObject = new Worker(); 
      Thread workerThread = new Thread(workerObject.DoWork); 

      // Start the worker thread. 
      workerThread.Start(url); 
      tlist.Add(workerThread); 
     } 
    } 
} 

public class Worker 
{ 
    // This method will be called when the thread is started. 
    public void DoWork(string url) 
    { 
     // prepare the web page we will be asking for 
     HttpWebRequest request = (HttpWebRequest) 
      WebRequest.Create(url); 

     // execute the request 
     HttpWebResponse response = (HttpWebResponse) 
      request.GetResponse(); 

     // we will read data via the response stream 
     Stream resStream = response.GetResponseStream(); 

     // process stream 
    } 
} 

Teraz muszę znaleźć optymalny sposób anulowania wszystkich wniosków.

Jednym ze sposobów jest przekonwertowanie każdego synchronicznego WebRequest na asynchroniczny i użycie WebRequest.Abort do anulowania przetwarzania.

Innym sposobem jest zwolnienie wskaźników nici i umożliwienie zginania wszystkich wątków za pomocą GC.

+0

„allow wszystkie wątki umrzeć za pomocą GC”. Nie tak zachowują się nici. Nawet jeśli nie ma odniesienia do utworzonego wątku, wątek nadal działa. – svick

+0

Jakie jest pytanie? – svick

+0

tak umrą po zakończeniu przetwarzania, w moim przypadku jest to do 20 sekund – walter

Odpowiedz

10

Jeśli chcesz pobrać 1000 plików, uruchomienie 1000 wątków na raz z pewnością nie jest najlepszą opcją. Nie tylko prawdopodobnie nie przyśpieszysz w porównaniu z pobieraniem tylko kilku plików na raz, ale będzie też wymagało co najmniej 1 GB pamięci wirtualnej. Tworzenie wątków jest kosztowne, staraj się tego unikać w pętli.

Zamiast tego należy użyć Parallel.ForEach() wraz z asynchronicznymi wersjami operacji żądania i odpowiedzi. Na przykład tak (kod WPF):

private void Start_Click(object sender, RoutedEventArgs e) 
{ 
    m_tokenSource = new CancellationTokenSource(); 
    var urls = …; 
    Task.Factory.StartNew(() => Start(urls, m_tokenSource.Token), m_tokenSource.Token); 
} 

private void Cancel_Click(object sender, RoutedEventArgs e) 
{ 
    m_tokenSource.Cancel(); 
} 

void Start(IEnumerable<string> urlList, CancellationToken token) 
{ 
    Parallel.ForEach(urlList, new ParallelOptions { CancellationToken = token }, 
        url => DownloadOne(url, token)); 

} 

void DownloadOne(string url, CancellationToken token) 
{ 
    ReportStart(url); 

    try 
    { 
     var request = WebRequest.Create(url); 

     var asyncResult = request.BeginGetResponse(null, null); 

     WaitHandle.WaitAny(new[] { asyncResult.AsyncWaitHandle, token.WaitHandle }); 

     if (token.IsCancellationRequested) 
     { 
      request.Abort(); 
      return; 
     } 

     var response = request.EndGetResponse(asyncResult); 

     using (var stream = response.GetResponseStream()) 
     { 
      byte[] bytes = new byte[4096]; 

      while (true) 
      { 
       asyncResult = stream.BeginRead(bytes, 0, bytes.Length, null, null); 

       WaitHandle.WaitAny(new[] { asyncResult.AsyncWaitHandle, 
              token.WaitHandle }); 

       if (token.IsCancellationRequested) 
        break; 

       var read = stream.EndRead(asyncResult); 

       if (read == 0) 
        break; 

       // do something with the downloaded bytes 
      } 
     } 

     response.Close(); 
    } 
    finally 
    { 
     ReportFinish(url); 
    } 
} 

W ten sposób, kiedy anulować operację, wszystkie pliki do pobrania są anulowane i żadne nowe są rozpoczęte. Prawdopodobnie też chcesz ustawić MaxDegreeOfParallelism z ParallelOptions, aby nie robić zbyt wielu pobrań na raz.

Nie jestem pewien, co chcesz zrobić z plikami, które pobierasz, więc używanie opcji StreamReader może być lepszą opcją.

+0

Nie widzę w tobie nici próbki przerwać lub zostawić na śmierć sposób przetwarzania, popraw mnie, jeśli jestem w błędzie; Wygląda na to, że konwersja synchronizowania webrequestu na asynchroniczne jest lepszym podejściem w tym scenariuszu; Sprawdziłem kod .net 4 i znalazłem kilka przykładów anulowania webrequestów i nic nie zostawiło samego wątku, więc najprawdopodobniej pójdzie tą drogą; dzięki – walter

+0

@walter, tak, myślę, że to jest lepsze w ten sposób. Po pierwsze, dlaczego chcesz "anulować" pobieranie, które faktycznie utrzymuje bieżące pobrane pliki? – svick

+0

Zauważ, że moja odpowiedź blokuje wątek, który pobiera. To nie jest idealne i teraz myślę, że powinno to zostać przepisane, szczególnie jeśli możesz użyć 'async' z C# 5. – svick

2

Myślę, że najlepszym rozwiązaniem jest "Parallel Foreach Cancellation". Sprawdź poniższy kod.

  1. Aby zaimplementować odwołania, należy najpierw dokonać CancellationTokenSource i przekazać go do Parallel.ForEach przez option.
  2. Jeśli chcesz anulować, możesz zadzwonić pod numer CancellationTokenSource.Cancel()
  3. Po anulowaniu nastąpi wyjątek OperationCanceledException, który należy obsłużyć.

Jest dobry artykuł o Parallel Programming związane z moją odpowiedź, która jest Task Parallel Library By Sacha Barber on CodeProject.

CancellationTokenSource tokenSource = new CancellationTokenSource(); 
ParallelOptions options = new ParallelOptions() 
{ 
    CancellationToken = tokenSource.Token 
}; 

List<string> urlList = null; 
//parallel foreach cancellation 
try 
{ 
    ParallelLoopResult result = Parallel.ForEach(urlList, options, (url) => 
    { 
     // Create the thread object. This does not start the thread. 
     Worker workerObject = new Worker(); 
     workerObject.DoWork(url); 
    }); 
} 
catch (OperationCanceledException ex) 
{ 
    Console.WriteLine("Operation Cancelled"); 
} 

AKTUALIZACJA

Poniższy kod jest "Parallel Foreach anulowania Kod Przykładowy".

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<int> data = ParallelEnumerable.Range(1, 10000).ToList(); 

     CancellationTokenSource tokenSource = new CancellationTokenSource(); 

     Task cancelTask = Task.Factory.StartNew(() => 
      { 
       Thread.Sleep(1000); 
       tokenSource.Cancel(); 
      }); 


     ParallelOptions options = new ParallelOptions() 
     { 
      CancellationToken = tokenSource.Token 
     }; 


     //parallel foreach cancellation 
     try 
     { 
      Parallel.ForEach(data,options, (x, state) => 
      { 
       Console.WriteLine(x); 
       Thread.Sleep(100); 
      }); 
     } 
     catch (OperationCanceledException ex) 
     { 
      Console.WriteLine("Operation Cancelled"); 
     } 


     Console.ReadLine(); 
    } 
} 
+0

To nie jest tak, jak anulowanie w TPL działa. I ten artykuł, który łączysz, wyjaśnia to. Jeśli twoje zadanie ma wspierać anulowanie, musisz ręcznie sprawdzić, czy jest ono anulowane. 'OperationCanceledException' nie jest wyrzucany automagicznie (robi to tylko' ThreadAbortException'). – svick

+0

@ Sick: Nie, nie jest. Jeśli użytkownik zadzwoni na "CancellationTokenSource.Cancel()", zostanie natychmiast anulowany po zakończeniu kroku w tym czasie. –

+0

Rozumiem. "Zadanie anulowania" jest takie jak wspomniano, ale anulowanie pętli równoległej i PLINQ są różne. jeśli Parallel Loop i PLINQ zostaną anulowane, wystąpi OperationCanceledException. –

Powiązane problemy