2011-07-16 5 views
7

私は、別々のスレッドでそれぞれ複数のWeb要求を処理するコンポーネントを持っています。各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 
    } 
} 

ここで、すべてのリクエストをキャンセルする最適な方法を見つける必要があります。

1つの方法は、各同期WebRequestを非同期に変換し、WebRequest.Abortを使用して処理をキャンセルすることです。

スレッドポインタを解放し、GCを使用してすべてのスレッドを終了させる方法もあります。

+0

で言及として殺すのAppDomainを産卵考慮することです。それはスレッドの振る舞いではありません。あなたが作成した 'Thread'への参照がなくても、スレッドはまだ動いています。 – svick

+0

質問は何ですか? – svick

+0

はい彼らは完了した処理の後に死ぬでしょう、私の場合は最大20秒です – walter

答えて

10

1000個のファイルをダウンロードする場合は、一度に1000個のスレッドを開始するのが最適な方法ではありません。一度にいくつかのファイルをダウンロードするのに比べてスピードアップが得られないだけでなく、少なくとも1 GBの仮想メモリが必要になります。スレッドを作成するのはコストがかかるので、ループを避けるようにしてください。

代わりに、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

私はあなたのサンプルスレッドの中止を見ていないか、処理方法を捨てるために残して、私が間違っている場合は私を修正してください。このシナリオでは、同期のWeb要求を非同期に変換する方が良いアプローチであることがあなたの要点です。私は.net 4のコードをチェックし、Webリクエストを取り消すサンプルをいくつか見つけました。ありがとう – walter

+0

@walter、はい、私はそれがこのように良いと思います。 1つは、ダウンロードを「キャンセル」したいのですが、実際に現在のダウンロードを実行し続けるのはなぜですか? – svick

+0

私の答えは、ダウンロードを行うスレッドをブロックすることに注意してください。これは理想的ではなく、特にC#5の 'async'を使うことができるならば、書き直すべきだと思います。 – svick

2

ベストソリューションは「パラレルフォーアッハーキャンセル」と思います。次のコードを確認してください。

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

UPDATED

次のコードでは、 "並列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が発生します。 –

関連する問題