2011-07-16 41 views
7

Tôi có một thành phần đang xử lý nhiều yêu cầu web trên từng chuỗi riêng biệt. Mỗi xử lý WebRequest là đồng bộ.Cách tiếp cận tốt hơn trong quản lý nhiều 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 
    } 
} 

Bây giờ tôi phải tìm cách tối ưu cách hủy tất cả yêu cầu.

Một cách là chuyển đổi mỗi WebRequest đồng bộ thành không đồng bộ và sử dụng WebRequest.Abort để hủy xử lý.

Cách khác là giải phóng con trỏ chuỗi và cho phép tất cả các chuỗi chết bằng cách sử dụng GC.

+0

“cho phép tất cả các chủ đề để chết sử dụng GC”. Đó không phải là cách hoạt động của luồng. Ngay cả khi không có tham chiếu đến 'Thread' mà bạn đã tạo, luồng vẫn đang chạy. – svick

+0

Câu hỏi là gì? – svick

+0

có họ sẽ chết sau khi hoàn thành xử lý, trong trường hợp của tôi là lên đến 20 giây – walter

Trả lời

10

Nếu bạn muốn tải xuống 1000 tệp, bắt đầu 1000 chủ đề cùng một lúc chắc chắn không phải là lựa chọn tốt nhất. Không chỉ có thể nó sẽ không giúp bạn tăng tốc khi so sánh với việc tải xuống chỉ một vài tệp cùng một lúc, nó cũng sẽ yêu cầu ít nhất 1 GB bộ nhớ ảo. Tạo chủ đề là tốn kém, cố gắng tránh làm như vậy trong một vòng lặp.

Điều bạn nên làm thay vào đó là sử dụng Parallel.ForEach() cùng với các phiên bản không đồng bộ của yêu cầu và hoạt động phản hồi. Ví dụ như thế này (mã 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); 
    } 
} 

Bằng cách này, khi bạn hủy thao tác, tất cả tải xuống sẽ bị hủy và không có chương trình mới nào được khởi động. Ngoài ra, bạn có thể muốn đặt MaxDegreeOfParallelism của ParallelOptions để bạn không thực hiện quá nhiều lượt tải xuống cùng một lúc.

Tôi không chắc chắn bạn muốn làm gì với các tệp bạn đang tải xuống, vì vậy việc sử dụng StreamReader có thể là một tùy chọn tốt hơn.

+0

tôi không thấy trong bạn mẫu hủy bỏ hoặc để lại để chết cách xử lý, sửa tôi nếu tôi sai; có vẻ như điểm của bạn chuyển đổi yêu cầu web đồng bộ hóa thành async là cách tiếp cận tốt hơn trong trường hợp này; tôi đã kiểm tra .net 4 mã và tìm thấy một vài mẫu hủy yêu cầu web và không có gì về để lại thread để chết của chính nó rất có thể sẽ đi con đường đó; cảm ơn – walter

+0

@walter, vâng, tôi nghĩ nó tốt hơn theo cách này. Đối với một, tại sao bạn muốn "hủy bỏ" một tải xuống, mà thực sự giữ tải hiện tại đang chạy? – svick

+0

Lưu ý rằng câu trả lời của tôi chặn chuỗi tải xuống. Đây không phải là lý tưởng và bây giờ tôi nghĩ rằng nó nên được viết lại, đặc biệt là nếu bạn có thể sử dụng 'async' từ C# 5. – svick

2

Tôi nghĩ giải pháp tốt nhất là "Hủy bỏ song song foreach". Vui lòng kiểm tra mã sau đây.

  1. Để thực hiện hủy, trước tiên bạn thực hiện CancellationTokenSource và chuyển nó đến Parallel.ForEach qua option.
  2. Nếu bạn muốn hủy, bạn có thể gọi CancellationTokenSource.Cancel()
  3. Sau khi hủy, OperationCanceledException sẽ xảy ra, bạn cần xử lý.

Có một bài viết tốt về Parallel Programming liên quan đến câu trả lời của tôi, đó là 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"); 
} 

CẬP NHẬT

Các mã sau đây là "Parallel Foreach Hủy Sample Code".

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

Đó không phải là cách hủy bỏ trong TPL hoạt động. Và bài viết bạn liên kết để giải thích điều đó. Nếu nhiệm vụ của bạn có nghĩa vụ hỗ trợ hủy, bạn phải tự kiểm tra xem nó có bị hủy hay không. 'OperationCanceledException' không được ném tự động (chỉ' ThreadAbortException' làm điều đó). – svick

+0

@svick: Không, không phải vậy. Nếu người dùng gọi 'CancellationTokenSource.Cancel()', nó sẽ bị hủy ngay lập tức sau khi kết thúc bước tại thời điểm đó. –

+0

Tôi hiểu. 'Task Cancellation' như bạn đã đề cập, nhưng việc hủy bỏ Parallel Loop và PLINQ là khác nhau. nếu Parallel Loop và PLINQ bị hủy, OperationCanceledException sẽ xuất hiện. –

Các vấn đề liên quan