2011-07-16 4 views
7

각각 별도의 스레드에서 여러 웹 요청을 처리하는 구성 요소가 있습니다. 각 WebRequest 처리는 동기식입니다.여러 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 
    } 
} 

이제 모든 요청을 취소하는 최적의 방법을 찾아야합니다.

한 가지 방법은 각 동기 WebRequest를 비동기로 변환하고 WebRequest.Abort를 사용하여 처리를 취소하는 것입니다.

또 다른 방법은 스레드 포인터를 해제하고 모든 스레드가 GC를 사용하여 죽는 것을 허용하는 것입니다.

+0

에 의해 언급으로 죽이는 응용 프로그램 도메인을 산란 고려하는 것입니다. 그것은 쓰레드가 동작하는 방식이 아닙니다. 생성 한'Thread'에 대한 참조가 없더라도 스레드는 여전히 실행 중입니다. – svick

+0

무엇이 문제입니까? – svick

+0

예 완료 처리 후 죽을 것입니다. 제 경우 최대 20 초입니다. – walter

답변

10

1000 개의 파일을 다운로드하려면 1000 개의 스레드를 동시에 시작하는 것이 가장 좋은 방법은 아닙니다. 한 번에 파일 몇 개를 다운로드하는 것과 비교할 때 속도 향상을 얻지 못할뿐만 아니라 최소 1GB의 가상 메모리가 필요합니다. 쓰래드를 생성하는 것은 비용이 많이 들기 때문에 루프를 피하는 것이 좋습니다.

대신 수행해야 할 작업은 비동기 버전의 요청 및 응답 작업과 함께 Parallel.ForEach()을 사용하는 것입니다. 이 (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); 
    } 
} 

이런 식으로, 모든 다운로드를 취소하고 새로운 사람이 시작되지 않습니다. 또한 한 번에 너무 많은 다운로드를 수행하지 않도록 MaxDegreeOfParallelismParallelOptions으로 설정하려고합니다.

다운로드하는 파일로 무엇을하고 싶은지 잘 모르겠습니다. 따라서 StreamReader을 사용하는 것이 더 나은 옵션 일 수 있습니다.

+0

나는 당신이 샘플 스레드를 중단하거나 처리 방법을 죽을두고 보지 않는다, 내가 잘못하면 나를 수정하십시오; 이 시나리오에서는 비동기식으로 sync webrequest를 변환하는 것이 더 나은 접근 방식이라고 생각합니다. 나는 .net 4 코드를 검사하고 웹 요청을 취소하는 샘플을 발견했으며 스레드 자체를 죽일 때까지 남겨 두지 않으므로 대부분 그 경로로 이동합니다. 고마워요 – walter

+0

@ 월터, 네,이 방법이 더 좋을 것 같아요. 하나는 다운로드를 "취소"하고 현재 다운로드를 계속 실행하는 이유는 무엇입니까? – svick

+0

내 대답은 다운로드를 수행하는 스레드를 차단한다는 점에 유의하십시오. 이것은 이상적인 것이 아니며, 특히 C# 5에서'async'를 사용할 수 있다면 재 작성해야한다고 생각합니다. – svick

2

가장 좋은 해결책은 "병렬 Foreach 취소"입니다. 다음 코드를 확인하십시오.

  1. 는 취소를 구현하려면 먼저 CancellationTokenSource을하고 option 통해 Parallel.ForEach에 전달합니다.
  2. 취소하려는 경우 CancellationTokenSource.Cancel()
  3. 취소 후 OperationCanceledException이 발생하여 처리해야합니다.

Task Parallel Library By Sacha Barber on CodeProject 내 대답에 관련된 Parallel Programming에 대한 좋은 기사가있다.


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"); } 

업데이트]

다음 코드는 "병렬의 Foreach 취소 샘플 코드"입니다.

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

TPL에서의 취소가 어떻게 작동하는지가 아닙니다. 그리고 당신이 링크 한 기사가 그것을 설명합니다. 작업이 취소를 지원해야하는 경우 수동으로 취소 여부를 확인해야합니다. 'OperationCanceledException'는 자동적으로 던지지 않습니다 ('ThreadAbortException'만이이를 수행합니다). – svick

+0

@svick : 아니에요. 사용자가'CancellationTokenSource.Cancel()'을 호출하면 그 시점에 단계를 종료 한 후 즉시 취소됩니다. –

+0

알겠습니다. 'Task Cancellation '은 언급 한 바와 같지만 Parallel Loop와 PLINQ의 취소는 다릅니다. Parallel Loop 및 PLINQ가 취소되면 OperationCanceledException이 발생합니다. –

관련 문제